waybetter 0.1.1 → 0.7.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 (537) hide show
  1. package/README.md +139 -6
  2. package/bin/waybetter.mjs +43 -0
  3. package/build/runtime.mjs +105 -0
  4. package/cli/buildenv.mjs +130 -0
  5. package/cli/dev.mjs +97 -0
  6. package/cli/i18n.mjs +382 -0
  7. package/cli/manifests.mjs +383 -0
  8. package/cli/release.mjs +437 -0
  9. package/cli/releasecheck.mjs +15 -0
  10. package/cli/remote.mjs +260 -0
  11. package/cli/store.mjs +378 -0
  12. package/cli/strings.mjs +123 -0
  13. package/dist/runtime/column.d.ts +93 -0
  14. package/dist/runtime/column.js +451 -0
  15. package/dist/runtime/column.js.map +1 -0
  16. package/dist/runtime/commandReport.d.ts +19 -0
  17. package/dist/runtime/commandReport.js +47 -0
  18. package/dist/runtime/commandReport.js.map +1 -0
  19. package/dist/runtime/commands.d.ts +72 -0
  20. package/dist/runtime/commands.js +214 -0
  21. package/dist/runtime/commands.js.map +1 -0
  22. package/dist/runtime/devErrors.d.ts +16 -0
  23. package/dist/runtime/devErrors.js +30 -0
  24. package/dist/runtime/devErrors.js.map +1 -0
  25. package/dist/runtime/domHub.d.ts +21 -0
  26. package/dist/runtime/domHub.js +139 -4
  27. package/dist/runtime/domHub.js.map +1 -1
  28. package/dist/runtime/featureModules.d.ts +157 -0
  29. package/dist/runtime/featureModules.js +484 -0
  30. package/dist/runtime/featureModules.js.map +1 -0
  31. package/dist/runtime/featureTable.d.ts +144 -0
  32. package/dist/runtime/featureTable.js +223 -0
  33. package/dist/runtime/featureTable.js.map +1 -0
  34. package/dist/runtime/fonts.d.ts +22 -0
  35. package/dist/runtime/fonts.js +72 -0
  36. package/dist/runtime/fonts.js.map +1 -0
  37. package/dist/runtime/i18n.d.ts +138 -0
  38. package/dist/runtime/i18n.js +614 -0
  39. package/dist/runtime/i18n.js.map +1 -0
  40. package/dist/runtime/i18nCheck.d.ts +30 -0
  41. package/dist/runtime/i18nCheck.js +224 -0
  42. package/dist/runtime/i18nCheck.js.map +1 -0
  43. package/dist/runtime/pageLang.d.ts +8 -0
  44. package/dist/runtime/pageLang.js +64 -0
  45. package/dist/runtime/pageLang.js.map +1 -0
  46. package/dist/runtime/pageStrings.d.ts +35 -0
  47. package/dist/runtime/pageStrings.js +76 -0
  48. package/dist/runtime/pageStrings.js.map +1 -0
  49. package/dist/runtime/panelButton.d.ts +33 -0
  50. package/dist/runtime/panelButton.js +83 -0
  51. package/dist/runtime/panelButton.js.map +1 -0
  52. package/dist/runtime/permissions.d.ts +47 -0
  53. package/dist/runtime/permissions.js +141 -0
  54. package/dist/runtime/permissions.js.map +1 -0
  55. package/dist/runtime/pluginApi.d.ts +51 -2
  56. package/dist/runtime/pluginApi.js +137 -22
  57. package/dist/runtime/pluginApi.js.map +1 -1
  58. package/dist/runtime/pluginI18n.d.ts +29 -0
  59. package/dist/runtime/pluginI18n.js +119 -0
  60. package/dist/runtime/pluginI18n.js.map +1 -0
  61. package/dist/runtime/settingsForm.d.ts +66 -0
  62. package/dist/runtime/settingsForm.js +114 -0
  63. package/dist/runtime/settingsForm.js.map +1 -0
  64. package/dist/runtime/site.d.ts +5 -0
  65. package/dist/runtime/site.js +1 -1
  66. package/dist/runtime/site.js.map +1 -1
  67. package/dist/runtime/siteRuntime.d.ts +144 -0
  68. package/dist/runtime/siteRuntime.js +175 -0
  69. package/dist/runtime/siteRuntime.js.map +1 -0
  70. package/dist/runtime/stringPackHook.d.ts +84 -0
  71. package/dist/runtime/stringPackHook.js +220 -0
  72. package/dist/runtime/stringPackHook.js.map +1 -0
  73. package/dist/runtime/stringPackTranslate.d.ts +61 -0
  74. package/dist/runtime/stringPackTranslate.js +267 -0
  75. package/dist/runtime/stringPackTranslate.js.map +1 -0
  76. package/dist/runtime/stringPacks.d.ts +248 -0
  77. package/dist/runtime/stringPacks.js +610 -0
  78. package/dist/runtime/stringPacks.js.map +1 -0
  79. package/dist/runtime/styles.d.ts +15 -0
  80. package/dist/runtime/styles.js +81 -0
  81. package/dist/runtime/styles.js.map +1 -0
  82. package/dist/runtime/textNet.d.ts +15 -0
  83. package/dist/runtime/textNet.js +131 -0
  84. package/dist/runtime/textNet.js.map +1 -0
  85. package/dist/runtime/themeApi.d.ts +121 -0
  86. package/dist/runtime/themeApi.js +220 -0
  87. package/dist/runtime/themeApi.js.map +1 -0
  88. package/dist/runtime/themeBar.d.ts +28 -0
  89. package/dist/runtime/themeBar.js +82 -0
  90. package/dist/runtime/themeBar.js.map +1 -0
  91. package/dist/runtime/themeChoice.d.ts +52 -0
  92. package/dist/runtime/themeChoice.js +69 -0
  93. package/dist/runtime/themeChoice.js.map +1 -0
  94. package/dist/runtime/themeComponents.d.ts +435 -0
  95. package/dist/runtime/themeComponents.js +476 -0
  96. package/dist/runtime/themeComponents.js.map +1 -0
  97. package/dist/runtime/themeDecor.d.ts +73 -0
  98. package/dist/runtime/themeDecor.js +133 -0
  99. package/dist/runtime/themeDecor.js.map +1 -0
  100. package/dist/runtime/themeDerive.d.ts +69 -0
  101. package/dist/runtime/themeDerive.js +117 -0
  102. package/dist/runtime/themeDerive.js.map +1 -0
  103. package/dist/runtime/themeDoctor.d.ts +52 -0
  104. package/dist/runtime/themeDoctor.js +112 -0
  105. package/dist/runtime/themeDoctor.js.map +1 -0
  106. package/dist/runtime/themeGuards.d.ts +60 -0
  107. package/dist/runtime/themeGuards.js +235 -0
  108. package/dist/runtime/themeGuards.js.map +1 -0
  109. package/dist/runtime/themeIcons.d.ts +130 -0
  110. package/dist/runtime/themeIcons.js +271 -0
  111. package/dist/runtime/themeIcons.js.map +1 -0
  112. package/dist/runtime/themeInk.d.ts +37 -0
  113. package/dist/runtime/themeInk.js +111 -0
  114. package/dist/runtime/themeInk.js.map +1 -0
  115. package/dist/runtime/themeInvariants.d.ts +39 -0
  116. package/dist/runtime/themeInvariants.js +273 -0
  117. package/dist/runtime/themeInvariants.js.map +1 -0
  118. package/dist/runtime/themeRuntime.d.ts +114 -0
  119. package/dist/runtime/themeRuntime.js +570 -0
  120. package/dist/runtime/themeRuntime.js.map +1 -0
  121. package/dist/runtime/themeSchema.d.ts +10 -0
  122. package/dist/runtime/themeSchema.js +150 -0
  123. package/dist/runtime/themeSchema.js.map +1 -0
  124. package/dist/runtime/themeSpec.d.ts +263 -0
  125. package/dist/runtime/themeSpec.js +435 -0
  126. package/dist/runtime/themeSpec.js.map +1 -0
  127. package/dist/runtime/themeTokens.d.ts +12 -0
  128. package/dist/runtime/themeTokens.js +14 -6
  129. package/dist/runtime/themeTokens.js.map +1 -1
  130. package/dist/runtime/themeVariants.d.ts +30 -0
  131. package/dist/runtime/themeVariants.js +53 -0
  132. package/dist/runtime/themeVariants.js.map +1 -0
  133. package/dist/runtime/ui.d.ts +192 -0
  134. package/dist/runtime/ui.js +926 -0
  135. package/dist/runtime/ui.js.map +1 -0
  136. package/dist/shell/InstallDialog.d.ts +40 -0
  137. package/dist/shell/InstallDialog.js +176 -0
  138. package/dist/shell/InstallDialog.js.map +1 -0
  139. package/dist/shell/StoreView.d.ts +78 -0
  140. package/dist/shell/StoreView.js +108 -0
  141. package/dist/shell/StoreView.js.map +1 -0
  142. package/dist/shell/addressModel.js.map +1 -1
  143. package/dist/shell/chrome.js.map +1 -1
  144. package/dist/shell/deckModel.d.ts +117 -0
  145. package/dist/shell/deckModel.js +267 -0
  146. package/dist/shell/deckModel.js.map +1 -0
  147. package/dist/shell/frame/AccountSwitcher.d.ts +42 -0
  148. package/dist/shell/frame/AccountSwitcher.js +243 -0
  149. package/dist/shell/frame/AccountSwitcher.js.map +1 -0
  150. package/dist/shell/frame/AddColumnDialog.d.ts +8 -0
  151. package/dist/shell/frame/AddColumnDialog.js +81 -0
  152. package/dist/shell/frame/AddColumnDialog.js.map +1 -0
  153. package/dist/shell/frame/AppMenu.d.ts +44 -0
  154. package/dist/shell/frame/AppMenu.js +721 -0
  155. package/dist/shell/frame/AppMenu.js.map +1 -0
  156. package/dist/shell/frame/CommandPalette.d.ts +7 -0
  157. package/dist/shell/frame/CommandPalette.js +118 -0
  158. package/dist/shell/frame/CommandPalette.js.map +1 -0
  159. package/dist/shell/frame/DeckView.d.ts +13 -0
  160. package/dist/shell/frame/DeckView.js +287 -0
  161. package/dist/shell/frame/DeckView.js.map +1 -0
  162. package/dist/shell/frame/FindBar.d.ts +10 -0
  163. package/dist/shell/frame/FindBar.js +108 -0
  164. package/dist/shell/frame/FindBar.js.map +1 -0
  165. package/dist/shell/frame/MediaPanel.d.ts +8 -0
  166. package/dist/shell/frame/MediaPanel.js +76 -0
  167. package/dist/shell/frame/MediaPanel.js.map +1 -0
  168. package/dist/shell/frame/NewTabPage.d.ts +9 -0
  169. package/dist/shell/frame/NewTabPage.js +71 -0
  170. package/dist/shell/frame/NewTabPage.js.map +1 -0
  171. package/dist/shell/frame/PaneTheme.d.ts +16 -0
  172. package/dist/shell/frame/PaneTheme.js +67 -0
  173. package/dist/shell/frame/PaneTheme.js.map +1 -0
  174. package/dist/shell/frame/ReportDialog.d.ts +3 -0
  175. package/dist/shell/frame/ReportDialog.js +95 -0
  176. package/dist/shell/frame/ReportDialog.js.map +1 -0
  177. package/dist/shell/frame/Shell.d.ts +9 -0
  178. package/dist/shell/frame/Shell.js +1139 -0
  179. package/dist/shell/frame/Shell.js.map +1 -0
  180. package/dist/shell/frame/Titlebar.d.ts +17 -0
  181. package/dist/shell/frame/Titlebar.js +79 -0
  182. package/dist/shell/frame/Titlebar.js.map +1 -0
  183. package/dist/shell/frame/TitlebarExtras.d.ts +4 -0
  184. package/dist/shell/frame/TitlebarExtras.js +202 -0
  185. package/dist/shell/frame/TitlebarExtras.js.map +1 -0
  186. package/dist/shell/frame/WebPanel.d.ts +4 -0
  187. package/dist/shell/frame/WebPanel.js +185 -0
  188. package/dist/shell/frame/WebPanel.js.map +1 -0
  189. package/dist/shell/frame/accountModel.d.ts +49 -0
  190. package/dist/shell/frame/accountModel.js +67 -0
  191. package/dist/shell/frame/accountModel.js.map +1 -0
  192. package/dist/shell/frame/accountSwitcher.css +91 -0
  193. package/dist/shell/frame/appI18n.d.ts +44 -0
  194. package/dist/shell/frame/appI18n.js +64 -0
  195. package/dist/shell/frame/appI18n.js.map +1 -0
  196. package/dist/shell/frame/appMenu.css +127 -0
  197. package/dist/shell/frame/backupModel.d.ts +38 -0
  198. package/dist/shell/frame/backupModel.js +192 -0
  199. package/dist/shell/frame/backupModel.js.map +1 -0
  200. package/dist/shell/frame/boot.d.ts +8 -0
  201. package/dist/shell/frame/boot.js +37 -0
  202. package/dist/shell/frame/boot.js.map +1 -0
  203. package/dist/shell/frame/brand.d.ts +1 -0
  204. package/dist/shell/frame/brand.js +4 -0
  205. package/dist/shell/frame/brand.js.map +1 -0
  206. package/dist/shell/frame/bridge.d.ts +35 -0
  207. package/dist/shell/frame/bridge.js +70 -0
  208. package/dist/shell/frame/bridge.js.map +1 -0
  209. package/dist/shell/frame/chromeTheme.d.ts +47 -0
  210. package/dist/shell/frame/chromeTheme.js +118 -0
  211. package/dist/shell/frame/chromeTheme.js.map +1 -0
  212. package/dist/shell/frame/deck.css +209 -0
  213. package/dist/shell/frame/deckStore.d.ts +89 -0
  214. package/dist/shell/frame/deckStore.js +239 -0
  215. package/dist/shell/frame/deckStore.js.map +1 -0
  216. package/dist/shell/frame/devHost.d.ts +35 -0
  217. package/dist/shell/frame/devHost.js +80 -0
  218. package/dist/shell/frame/devHost.js.map +1 -0
  219. package/dist/shell/frame/devModel.d.ts +7 -0
  220. package/dist/shell/frame/devModel.js +17 -0
  221. package/dist/shell/frame/devModel.js.map +1 -0
  222. package/dist/shell/frame/diag.d.ts +7 -0
  223. package/dist/shell/frame/diag.js +39 -0
  224. package/dist/shell/frame/diag.js.map +1 -0
  225. package/dist/shell/frame/edges.css +159 -0
  226. package/dist/shell/frame/findBar.css +14 -0
  227. package/dist/shell/frame/frameMessages.d.ts +248 -0
  228. package/dist/shell/frame/frameMessages.js +252 -0
  229. package/dist/shell/frame/frameMessages.js.map +1 -0
  230. package/dist/shell/frame/hooks.d.ts +39 -0
  231. package/dist/shell/frame/hooks.js +97 -0
  232. package/dist/shell/frame/hooks.js.map +1 -0
  233. package/dist/shell/frame/i18n.d.ts +27 -0
  234. package/dist/shell/frame/i18n.js +66 -0
  235. package/dist/shell/frame/i18n.js.map +1 -0
  236. package/dist/shell/frame/iconPack.d.ts +11 -0
  237. package/dist/shell/frame/iconPack.js +45 -0
  238. package/dist/shell/frame/iconPack.js.map +1 -0
  239. package/dist/shell/frame/icons.d.ts +71 -0
  240. package/dist/shell/frame/icons.js +75 -0
  241. package/dist/shell/frame/icons.js.map +1 -0
  242. package/dist/shell/frame/index.d.ts +34 -0
  243. package/dist/shell/frame/index.js +43 -0
  244. package/dist/shell/frame/index.js.map +1 -0
  245. package/dist/shell/frame/language.d.ts +58 -0
  246. package/dist/shell/frame/language.js +165 -0
  247. package/dist/shell/frame/language.js.map +1 -0
  248. package/dist/shell/frame/mediaModel.d.ts +47 -0
  249. package/dist/shell/frame/mediaModel.js +67 -0
  250. package/dist/shell/frame/mediaModel.js.map +1 -0
  251. package/dist/shell/frame/menuMessages.en.json +96 -0
  252. package/dist/shell/frame/menuModel.d.ts +319 -0
  253. package/dist/shell/frame/menuModel.js +301 -0
  254. package/dist/shell/frame/menuModel.js.map +1 -0
  255. package/dist/shell/frame/nativeMenu.d.ts +5 -0
  256. package/dist/shell/frame/nativeMenu.js +178 -0
  257. package/dist/shell/frame/nativeMenu.js.map +1 -0
  258. package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
  259. package/dist/shell/frame/nativeMenuModel.js +186 -0
  260. package/dist/shell/frame/nativeMenuModel.js.map +1 -0
  261. package/dist/shell/frame/paletteModel.d.ts +139 -0
  262. package/dist/shell/frame/paletteModel.js +114 -0
  263. package/dist/shell/frame/paletteModel.js.map +1 -0
  264. package/dist/shell/frame/paneTheme.css +9 -0
  265. package/dist/shell/frame/pluginCommandStore.d.ts +23 -0
  266. package/dist/shell/frame/pluginCommandStore.js +70 -0
  267. package/dist/shell/frame/pluginCommandStore.js.map +1 -0
  268. package/dist/shell/frame/reportModel.d.ts +52 -0
  269. package/dist/shell/frame/reportModel.js +105 -0
  270. package/dist/shell/frame/reportModel.js.map +1 -0
  271. package/dist/shell/frame/safeStore.d.ts +7 -0
  272. package/dist/shell/frame/safeStore.js +40 -0
  273. package/dist/shell/frame/safeStore.js.map +1 -0
  274. package/dist/shell/frame/searchInput.d.ts +2 -0
  275. package/dist/shell/frame/searchInput.js +11 -0
  276. package/dist/shell/frame/searchInput.js.map +1 -0
  277. package/dist/shell/frame/shell.css +634 -0
  278. package/dist/shell/frame/site.d.ts +206 -0
  279. package/dist/shell/frame/site.js +22 -0
  280. package/dist/shell/frame/site.js.map +1 -0
  281. package/dist/shell/frame/state.d.ts +101 -0
  282. package/dist/shell/frame/state.js +286 -0
  283. package/dist/shell/frame/state.js.map +1 -0
  284. package/dist/shell/frame/syncGate.d.ts +10 -0
  285. package/dist/shell/frame/syncGate.js +36 -0
  286. package/dist/shell/frame/syncGate.js.map +1 -0
  287. package/dist/shell/frame/syncHost.d.ts +35 -0
  288. package/dist/shell/frame/syncHost.js +85 -0
  289. package/dist/shell/frame/syncHost.js.map +1 -0
  290. package/dist/shell/frame/tabStill.d.ts +2 -0
  291. package/dist/shell/frame/tabStill.js +62 -0
  292. package/dist/shell/frame/tabStill.js.map +1 -0
  293. package/dist/shell/frame/themeScopes.d.ts +29 -0
  294. package/dist/shell/frame/themeScopes.js +61 -0
  295. package/dist/shell/frame/themeScopes.js.map +1 -0
  296. package/dist/shell/frame/themeStore.d.ts +99 -0
  297. package/dist/shell/frame/themeStore.js +267 -0
  298. package/dist/shell/frame/themeStore.js.map +1 -0
  299. package/dist/shell/frame/windowBinding.d.ts +2 -0
  300. package/dist/shell/frame/windowBinding.js +7 -0
  301. package/dist/shell/frame/windowBinding.js.map +1 -0
  302. package/dist/shell/frame/workspaceModel.d.ts +82 -0
  303. package/dist/shell/frame/workspaceModel.js +250 -0
  304. package/dist/shell/frame/workspaceModel.js.map +1 -0
  305. package/dist/shell/frame/workspaces.css +84 -0
  306. package/dist/shell/frame/workspaces.d.ts +22 -0
  307. package/dist/shell/frame/workspaces.js +125 -0
  308. package/dist/shell/frame/workspaces.js.map +1 -0
  309. package/dist/shell/frame/zoomStore.d.ts +10 -0
  310. package/dist/shell/frame/zoomStore.js +65 -0
  311. package/dist/shell/frame/zoomStore.js.map +1 -0
  312. package/dist/shell/installController.d.ts +137 -0
  313. package/dist/shell/installController.js +172 -0
  314. package/dist/shell/installController.js.map +1 -0
  315. package/dist/shell/installerMessages.d.ts +30 -0
  316. package/dist/shell/installerMessages.js +36 -0
  317. package/dist/shell/installerMessages.js.map +1 -0
  318. package/dist/shell/linkDrop.js.map +1 -1
  319. package/dist/shell/messages.d.ts +16 -0
  320. package/dist/shell/messages.js +27 -0
  321. package/dist/shell/messages.js.map +1 -0
  322. package/dist/shell/palette.js.map +1 -1
  323. package/dist/shell/paneModel.d.ts +22 -0
  324. package/dist/shell/paneModel.js +24 -1
  325. package/dist/shell/paneModel.js.map +1 -1
  326. package/dist/shell/panel/Appearance.d.ts +8 -0
  327. package/dist/shell/panel/Appearance.js +265 -0
  328. package/dist/shell/panel/Appearance.js.map +1 -0
  329. package/dist/shell/panel/BackupPanel.d.ts +5 -0
  330. package/dist/shell/panel/BackupPanel.js +198 -0
  331. package/dist/shell/panel/BackupPanel.js.map +1 -0
  332. package/dist/shell/panel/DeveloperPanel.d.ts +4 -0
  333. package/dist/shell/panel/DeveloperPanel.js +51 -0
  334. package/dist/shell/panel/DeveloperPanel.js.map +1 -0
  335. package/dist/shell/panel/FeatureToggles.d.ts +13 -0
  336. package/dist/shell/panel/FeatureToggles.js +42 -0
  337. package/dist/shell/panel/FeatureToggles.js.map +1 -0
  338. package/dist/shell/panel/Home.d.ts +3 -0
  339. package/dist/shell/panel/Home.js +23 -0
  340. package/dist/shell/panel/Home.js.map +1 -0
  341. package/dist/shell/panel/IconPackInstall.d.ts +15 -0
  342. package/dist/shell/panel/IconPackInstall.js +41 -0
  343. package/dist/shell/panel/IconPackInstall.js.map +1 -0
  344. package/dist/shell/panel/LanguageSettings.d.ts +1 -0
  345. package/dist/shell/panel/LanguageSettings.js +54 -0
  346. package/dist/shell/panel/LanguageSettings.js.map +1 -0
  347. package/dist/shell/panel/MakePanel.d.ts +3 -0
  348. package/dist/shell/panel/MakePanel.js +237 -0
  349. package/dist/shell/panel/MakePanel.js.map +1 -0
  350. package/dist/shell/panel/MarkdownDoc.d.ts +6 -0
  351. package/dist/shell/panel/MarkdownDoc.js +35 -0
  352. package/dist/shell/panel/MarkdownDoc.js.map +1 -0
  353. package/dist/shell/panel/Modes.d.ts +40 -0
  354. package/dist/shell/panel/Modes.js +270 -0
  355. package/dist/shell/panel/Modes.js.map +1 -0
  356. package/dist/shell/panel/NavIcon.d.ts +4 -0
  357. package/dist/shell/panel/NavIcon.js +6 -0
  358. package/dist/shell/panel/NavIcon.js.map +1 -0
  359. package/dist/shell/panel/Panel.d.ts +7 -0
  360. package/dist/shell/panel/Panel.js +139 -0
  361. package/dist/shell/panel/Panel.js.map +1 -0
  362. package/dist/shell/panel/PluginFeatures.d.ts +24 -0
  363. package/dist/shell/panel/PluginFeatures.js +165 -0
  364. package/dist/shell/panel/PluginFeatures.js.map +1 -0
  365. package/dist/shell/panel/Plugins.d.ts +33 -0
  366. package/dist/shell/panel/Plugins.js +112 -0
  367. package/dist/shell/panel/Plugins.js.map +1 -0
  368. package/dist/shell/panel/PowerSettings.d.ts +4 -0
  369. package/dist/shell/panel/PowerSettings.js +52 -0
  370. package/dist/shell/panel/PowerSettings.js.map +1 -0
  371. package/dist/shell/panel/Settings.d.ts +23 -0
  372. package/dist/shell/panel/Settings.js +117 -0
  373. package/dist/shell/panel/Settings.js.map +1 -0
  374. package/dist/shell/panel/ShareSetup.d.ts +8 -0
  375. package/dist/shell/panel/ShareSetup.js +130 -0
  376. package/dist/shell/panel/ShareSetup.js.map +1 -0
  377. package/dist/shell/panel/StoreSection.d.ts +7 -0
  378. package/dist/shell/panel/StoreSection.js +78 -0
  379. package/dist/shell/panel/StoreSection.js.map +1 -0
  380. package/dist/shell/panel/StringPacks.d.ts +20 -0
  381. package/dist/shell/panel/StringPacks.js +131 -0
  382. package/dist/shell/panel/StringPacks.js.map +1 -0
  383. package/dist/shell/panel/ThemeBuilder.d.ts +5 -0
  384. package/dist/shell/panel/ThemeBuilder.js +205 -0
  385. package/dist/shell/panel/ThemeBuilder.js.map +1 -0
  386. package/dist/shell/panel/backup.css +34 -0
  387. package/dist/shell/panel/builder.css +240 -0
  388. package/dist/shell/panel/builderModel.d.ts +55 -0
  389. package/dist/shell/panel/builderModel.js +95 -0
  390. package/dist/shell/panel/builderModel.js.map +1 -0
  391. package/dist/shell/panel/compare.d.ts +10 -0
  392. package/dist/shell/panel/compare.js +51 -0
  393. package/dist/shell/panel/compare.js.map +1 -0
  394. package/dist/shell/panel/controls.d.ts +135 -0
  395. package/dist/shell/panel/controls.js +213 -0
  396. package/dist/shell/panel/controls.js.map +1 -0
  397. package/dist/shell/panel/featureModel.d.ts +69 -0
  398. package/dist/shell/panel/featureModel.js +79 -0
  399. package/dist/shell/panel/featureModel.js.map +1 -0
  400. package/dist/shell/panel/fonts/FontBrowser.d.ts +10 -0
  401. package/dist/shell/panel/fonts/FontBrowser.js +189 -0
  402. package/dist/shell/panel/fonts/FontBrowser.js.map +1 -0
  403. package/dist/shell/panel/fonts/FontRoles.d.ts +20 -0
  404. package/dist/shell/panel/fonts/FontRoles.js +77 -0
  405. package/dist/shell/panel/fonts/FontRoles.js.map +1 -0
  406. package/dist/shell/panel/fonts/catalog.d.ts +37 -0
  407. package/dist/shell/panel/fonts/catalog.js +68 -0
  408. package/dist/shell/panel/fonts/catalog.js.map +1 -0
  409. package/dist/shell/panel/fonts/googleFontsCatalog.json +1 -0
  410. package/dist/shell/panel/index.d.ts +20 -0
  411. package/dist/shell/panel/index.js +34 -0
  412. package/dist/shell/panel/index.js.map +1 -0
  413. package/dist/shell/panel/languageModel.d.ts +5 -0
  414. package/dist/shell/panel/languageModel.js +7 -0
  415. package/dist/shell/panel/languageModel.js.map +1 -0
  416. package/dist/shell/panel/make.css +73 -0
  417. package/dist/shell/panel/makeModel.d.ts +32 -0
  418. package/dist/shell/panel/makeModel.js +84 -0
  419. package/dist/shell/panel/makeModel.js.map +1 -0
  420. package/dist/shell/panel/markdown.d.ts +46 -0
  421. package/dist/shell/panel/markdown.js +100 -0
  422. package/dist/shell/panel/markdown.js.map +1 -0
  423. package/dist/shell/panel/nav.d.ts +12 -0
  424. package/dist/shell/panel/nav.js +50 -0
  425. package/dist/shell/panel/nav.js.map +1 -0
  426. package/dist/shell/panel/panel.css +401 -0
  427. package/dist/shell/panel/panelMessages.d.ts +803 -0
  428. package/dist/shell/panel/panelMessages.js +779 -0
  429. package/dist/shell/panel/panelMessages.js.map +1 -0
  430. package/dist/shell/panel/plugins.css +59 -0
  431. package/dist/shell/panel/setupModel.d.ts +87 -0
  432. package/dist/shell/panel/setupModel.js +152 -0
  433. package/dist/shell/panel/setupModel.js.map +1 -0
  434. package/dist/shell/panel/site.d.ts +259 -0
  435. package/dist/shell/panel/site.js +25 -0
  436. package/dist/shell/panel/site.js.map +1 -0
  437. package/dist/shell/panel/strings.css +7 -0
  438. package/dist/shell/panel/themes.d.ts +10 -0
  439. package/dist/shell/panel/themes.js +8 -0
  440. package/dist/shell/panel/themes.js.map +1 -0
  441. package/dist/shell/profileModel.d.ts +3 -2
  442. package/dist/shell/profileModel.js +7 -5
  443. package/dist/shell/profileModel.js.map +1 -1
  444. package/dist/shell/store.css +250 -0
  445. package/dist/shell/storeMessages.d.ts +193 -0
  446. package/dist/shell/storeMessages.js +196 -0
  447. package/dist/shell/storeMessages.js.map +1 -0
  448. package/dist/shell/storeModel.d.ts +205 -0
  449. package/dist/shell/storeModel.js +146 -0
  450. package/dist/shell/storeModel.js.map +1 -0
  451. package/dist/shell/stringPacksModel.d.ts +44 -0
  452. package/dist/shell/stringPacksModel.js +99 -0
  453. package/dist/shell/stringPacksModel.js.map +1 -0
  454. package/dist/shell/updateModel.d.ts +2 -1
  455. package/dist/shell/updateModel.js +5 -4
  456. package/dist/shell/updateModel.js.map +1 -1
  457. package/dist/shell/windowControls.d.ts +4 -0
  458. package/dist/shell/windowControls.js +5 -0
  459. package/dist/shell/windowControls.js.map +1 -1
  460. package/i18n-config.schema.json +29 -0
  461. package/i18n-meta.schema.json +26 -0
  462. package/i18n.schema.json +42 -0
  463. package/icons.schema.json +655 -0
  464. package/languages.schema.json +23 -0
  465. package/locales/ar-x-fm.json +11 -0
  466. package/locales/ar.json +1388 -0
  467. package/locales/bg.json +1387 -0
  468. package/locales/bn.json +1387 -0
  469. package/locales/ca.json +1387 -0
  470. package/locales/cs.json +1387 -0
  471. package/locales/da.json +1387 -0
  472. package/locales/de.json +1387 -0
  473. package/locales/el.json +1387 -0
  474. package/locales/en-GB.json +27 -0
  475. package/locales/en.json +1386 -0
  476. package/locales/en.meta.json +51 -0
  477. package/locales/es.json +1387 -0
  478. package/locales/eu.json +1387 -0
  479. package/locales/fa.json +1388 -0
  480. package/locales/fi.json +1387 -0
  481. package/locales/fil.json +1387 -0
  482. package/locales/fr.json +1387 -0
  483. package/locales/ga.json +1387 -0
  484. package/locales/gl.json +1387 -0
  485. package/locales/gu.json +1387 -0
  486. package/locales/ha.json +1387 -0
  487. package/locales/he.json +1388 -0
  488. package/locales/hi.json +1387 -0
  489. package/locales/hr.json +1387 -0
  490. package/locales/hu.json +1387 -0
  491. package/locales/id.json +1387 -0
  492. package/locales/ig.json +1387 -0
  493. package/locales/it.json +1387 -0
  494. package/locales/ja.json +1387 -0
  495. package/locales/kn.json +1387 -0
  496. package/locales/ko.json +1387 -0
  497. package/locales/mr.json +1387 -0
  498. package/locales/ms.json +1387 -0
  499. package/locales/nb.json +1387 -0
  500. package/locales/nl.json +1387 -0
  501. package/locales/pl.json +1387 -0
  502. package/locales/pt.json +1387 -0
  503. package/locales/ro.json +1387 -0
  504. package/locales/ru.json +1387 -0
  505. package/locales/sk.json +1387 -0
  506. package/locales/sr.json +1387 -0
  507. package/locales/sv.json +1387 -0
  508. package/locales/ta.json +1387 -0
  509. package/locales/th.json +1387 -0
  510. package/locales/tr.json +1387 -0
  511. package/locales/uk.json +1387 -0
  512. package/locales/ur.json +1388 -0
  513. package/locales/vi.json +1387 -0
  514. package/locales/yo.json +1387 -0
  515. package/locales/zh-Hans.json +1387 -0
  516. package/locales/zh-Hant.json +1387 -0
  517. package/package.json +138 -5
  518. package/store-catalog.schema.json +78 -0
  519. package/theme.schema.json +4668 -0
  520. package/windows/installer.nsi +946 -0
  521. package/windows/languages/Basque.nsh +28 -0
  522. package/windows/languages/Catalan.nsh +28 -0
  523. package/windows/languages/Croatian.nsh +28 -0
  524. package/windows/languages/Czech.nsh +28 -0
  525. package/windows/languages/Danish.nsh +28 -0
  526. package/windows/languages/Finnish.nsh +28 -0
  527. package/windows/languages/Galician.nsh +28 -0
  528. package/windows/languages/Greek.nsh +28 -0
  529. package/windows/languages/Hungarian.nsh +28 -0
  530. package/windows/languages/Indonesian.nsh +28 -0
  531. package/windows/languages/Irish.nsh +28 -0
  532. package/windows/languages/Malay.nsh +28 -0
  533. package/windows/languages/Polish.nsh +28 -0
  534. package/windows/languages/Romanian.nsh +28 -0
  535. package/windows/languages/Serbian.nsh +28 -0
  536. package/windows/languages/Slovak.nsh +28 -0
  537. package/windows/languages/Thai.nsh +28 -0
@@ -0,0 +1,112 @@
1
+ // The theme doctor: advice, never a block. A theme may do anything its trust level allows; the doctor
2
+ // points at the lighter mechanism for what the theme does (a token before a component setting, a
3
+ // component setting before CSS, CSS before a script) and at performance, accessibility and contrast
4
+ // costs. Used by the import screen, the Theme Builder's full report and `waybetter store check`.
5
+ // Pure. Every finding says where it is (a token path or the CSS rule), what was found and how to fix
6
+ // it; the words are catalog keys (`wb.doctor.<id>.found` / `.fix`, English below), so a panel shows
7
+ // them in the owner's language and the CLI prints the English.
8
+ import { contrastRatio } from "./themeApi.js";
9
+ import { ensureContrast } from "./themeSpec.js";
10
+ /** The doctor's words in English (the source of `wb.doctor.*` in the catalogs). */
11
+ export const DOCTOR_MESSAGES = {
12
+ 'wb.doctor.css-radius.found': 'This rule sets corner radii in CSS.',
13
+ 'wb.doctor.css-radius.fix': 'Set them with the radius of a part under components, or shape.radius for all of them: no CSS needed, and it keeps working when the site renames its classes.',
14
+ 'wb.doctor.css-bevel.found': 'This rule draws a bevelled edge with stacked inset shadows.',
15
+ 'wb.doctor.css-bevel.fix': 'Use borderStyle "bevel-out" or "bevel-in" on a part under components: it draws the bevel from the theme’s colors.',
16
+ 'wb.doctor.css-hide.found': 'This rule hides something with display: none.',
17
+ 'wb.doctor.css-hide.fix': 'Use visible on a part under components (avatars, counts, names) or a layout variant such as sidebar: hidden; the owner can then switch it back.',
18
+ 'wb.doctor.css-color.found': 'This rule uses a literal color.',
19
+ 'wb.doctor.css-color.fix': 'Name a role (text, muted, accent, surface…) in colors or in a component, so the contrast pass covers it.',
20
+ 'wb.doctor.css-font.found': 'This rule sets a font family.',
21
+ 'wb.doctor.css-font.fix': 'Use font.roles: it sets the post, heading, numbers and mono faces without CSS.',
22
+ 'wb.doctor.css-has.found': 'This rule uses :has(), which is matched again on every change of the page.',
23
+ 'wb.doctor.css-has.fix': 'Keep :has() off rules that repeat down a timeline, or anchor the rule on a part.',
24
+ 'wb.doctor.css-universal.found': 'This rule uses a universal selector (*), matched against every element.',
25
+ 'wb.doctor.css-universal.fix': 'Anchor the rule on a part instead.',
26
+ 'wb.doctor.css-blur.found': 'This rule blurs what is behind it (backdrop-filter), which is costly on parts that repeat.',
27
+ 'wb.doctor.css-blur.fix': 'Use the glass fill of bars and panels: it is the cheaper place for blur.',
28
+ 'wb.doctor.css-outline-none.found': 'This rule removes the focus outline, so keyboard focus is no longer visible.',
29
+ 'wb.doctor.css-outline-none.fix': 'Use decor.focusRing to draw a visible focus ring instead.',
30
+ 'wb.doctor.css-motion.found': 'This rule animates without checking for reduced motion.',
31
+ 'wb.doctor.css-motion.fix': 'Keep motion inside @media (prefers-reduced-motion: no-preference), or let decor.motion do it for you.',
32
+ 'wb.doctor.css-size.found': 'The CSS is more than 20,000 characters long.',
33
+ 'wb.doctor.css-size.fix': 'The site recalculates styles with every rule; tokens and component settings compile to fewer rules.',
34
+ 'wb.doctor.contrast.found': 'The {token} color was adjusted to stay readable ({ratio}:1 on the background).',
35
+ 'wb.doctor.contrast.fix': 'Pick a lighter or darker {token} color; until then the theme is shown with the adjusted one.',
36
+ 'wb.doctor.glass-post.found': 'Glass is set on posts, but it is not drawn: blur on every post of a timeline is too costly.',
37
+ 'wb.doctor.glass-post.fix': 'Use a solid or tinted fill for posts and keep glass for bars and panels.',
38
+ 'wb.doctor.small-post-text.found': 'Post text is {size}px, which is hard to read for many people.',
39
+ 'wb.doctor.small-post-text.fix': 'Use 14px or more for the post text size, or raise the text size of the whole theme.',
40
+ 'wb.doctor.nav-moved.found': 'The navigation is moved to the {position}, which changes where people look for it.',
41
+ 'wb.doctor.nav-moved.fix': 'It only applies on wide panes, and the owner can turn layout changes off. Keep the default position unless the layout needs it.',
42
+ };
43
+ const fill = (text, params) => text.replace(/\{(\w+)\}/g, (_, name) => params[name] ?? '');
44
+ /** What a theme's free-form CSS does that a v2 group does without CSS. */
45
+ const CSS_RULES = [
46
+ { id: 'css-radius', kind: 'lighter', test: /border-radius\s*:/gi, property: 'border-radius' },
47
+ { id: 'css-bevel', kind: 'lighter', test: /box-shadow\s*:[^;{}]*inset[^;{}]*inset[^;{}]*inset/gi, property: 'box-shadow' },
48
+ { id: 'css-hide', kind: 'lighter', test: /display\s*:\s*none/gi, property: 'display' },
49
+ { id: 'css-color', kind: 'lighter', test: /(?:^|[;{\s])(?:color|background(?:-color)?)\s*:\s*#[0-9a-f]{3,8}/gi, property: 'color' },
50
+ { id: 'css-font', kind: 'lighter', test: /font-family\s*:/gi, property: 'font-family' },
51
+ { id: 'css-has', kind: 'performance', test: /:has\(/gi, property: ':has()' },
52
+ { id: 'css-universal', kind: 'performance', test: /(?:^|[\s,>+~])\*\s*(?:[,{>+~:[]|$)/gm, property: '*' },
53
+ { id: 'css-blur', kind: 'performance', test: /backdrop-filter\s*:/gi, property: 'backdrop-filter' },
54
+ { id: 'css-outline-none', kind: 'accessibility', test: /outline\s*:\s*(?:none|0)\b/gi, property: 'outline' },
55
+ { id: 'css-motion', kind: 'accessibility', test: /(?:animation|transition)\s*:/gi, property: 'transition' },
56
+ ];
57
+ const MAX_PER_RULE = 25;
58
+ /** The selector of the rule block a position of `css` is in (the text before its `{`), shortened. */
59
+ function selectorAt(css, index) {
60
+ const open = css.lastIndexOf('{', index);
61
+ if (open < 0)
62
+ return '';
63
+ const close = Math.max(css.lastIndexOf('}', open - 1), css.lastIndexOf(';', open - 1), css.lastIndexOf('{', open - 1));
64
+ const selector = css.slice(close + 1, open).replace(/\s+/g, ' ').trim();
65
+ return selector.length > 80 ? `${selector.slice(0, 79)}…` : selector;
66
+ }
67
+ function advice(id, kind, where, params = {}, key = id) {
68
+ const words = DOCTOR_MESSAGES;
69
+ return { kind, id, key, where, params, text: fill(words[`wb.doctor.${key}.found`], params), fix: fill(words[`wb.doctor.${key}.fix`], params) };
70
+ }
71
+ /** Advice for a theme document: every finding, with where it is. Empty when there is nothing to say. */
72
+ export function themeDoctor(theme) {
73
+ const out = [];
74
+ const css = theme.css ?? '';
75
+ if (css.trim()) {
76
+ for (const rule of CSS_RULES) {
77
+ const seen = new Set();
78
+ for (const match of css.matchAll(rule.test)) {
79
+ const selector = selectorAt(css, match.index ?? 0) || 'css';
80
+ const where = rule.id === 'css-universal' || rule.id === 'css-has' ? selector : `${selector} { ${rule.property} }`;
81
+ if (seen.has(where))
82
+ continue;
83
+ seen.add(where);
84
+ out.push(advice(rule.id, rule.kind, where));
85
+ if (seen.size >= MAX_PER_RULE)
86
+ break;
87
+ }
88
+ }
89
+ if (css.length > 20_000)
90
+ out.push(advice('css-size', 'performance', 'css'));
91
+ }
92
+ if (theme.colors) {
93
+ const fixed = ensureContrast(theme.colors);
94
+ for (const role of ['text', 'muted', 'accent']) {
95
+ if (fixed[role] !== theme.colors[role]) {
96
+ out.push(advice(`contrast-${role}`, 'contrast', `colors.${role}`, { token: role, ratio: contrastRatio(theme.colors[role], theme.colors.bg).toFixed(2) }, 'contrast'));
97
+ }
98
+ }
99
+ }
100
+ if (theme.components) {
101
+ if (theme.components.post?.fill === 'glass')
102
+ out.push(advice('glass-post', 'performance', 'components.post.fill'));
103
+ const size = theme.components.postText?.fontSize;
104
+ if (size !== undefined && size < 14)
105
+ out.push(advice('small-post-text', 'accessibility', 'components.postText.fontSize', { size: String(size) }));
106
+ }
107
+ if (theme.variants?.nav === 'top' || theme.variants?.nav === 'bottom') {
108
+ out.push(advice('nav-moved', 'accessibility', 'variants.nav', { position: theme.variants.nav }));
109
+ }
110
+ return out;
111
+ }
112
+ //# sourceMappingURL=themeDoctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themeDoctor.js","sourceRoot":"","sources":["../../../runtime/src/themeDoctor.ts"],"names":[],"mappings":"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","sourcesContent":["// The theme doctor: advice, never a block. A theme may do anything its trust level allows; the doctor\n// points at the lighter mechanism for what the theme does (a token before a component setting, a\n// component setting before CSS, CSS before a script) and at performance, accessibility and contrast\n// costs. Used by the import screen, the Theme Builder's full report and `waybetter store check`.\n// Pure. Every finding says where it is (a token path or the CSS rule), what was found and how to fix\n// it; the words are catalog keys (`wb.doctor.<id>.found` / `.fix`, English below), so a panel shows\n// them in the owner's language and the CLI prints the English.\n\nimport { contrastRatio } from './themeApi.ts';\nimport { ensureContrast, type ThemeSpec } from './themeSpec.ts';\n\nexport type DoctorAdvice = {\n /** `lighter`: a lighter mechanism would do; `performance`, `accessibility`, `contrast`: a cost. */\n kind: 'lighter' | 'performance' | 'accessibility' | 'contrast';\n /** Stable id of the finding, for tests (`css-radius`, `contrast-text`). */\n id: string;\n /** Its words in the catalog: `wb.doctor.<key>.found` and `wb.doctor.<key>.fix` (`css-radius`, `contrast`). */\n key: string;\n /** A token path of the theme (`colors.text`, `components.postText.fontSize`) or the CSS rule (`.card { border-radius }`). */\n where: string;\n /** Values for the `{name}` slots of the words. */\n params: Record<string, string>;\n /** English: what was found. */\n text: string;\n /** English: how to fix it. */\n fix: string;\n};\n\n/** The doctor's words in English (the source of `wb.doctor.*` in the catalogs). */\nexport const DOCTOR_MESSAGES = {\n 'wb.doctor.css-radius.found': 'This rule sets corner radii in CSS.',\n 'wb.doctor.css-radius.fix': 'Set them with the radius of a part under components, or shape.radius for all of them: no CSS needed, and it keeps working when the site renames its classes.',\n 'wb.doctor.css-bevel.found': 'This rule draws a bevelled edge with stacked inset shadows.',\n 'wb.doctor.css-bevel.fix': 'Use borderStyle \"bevel-out\" or \"bevel-in\" on a part under components: it draws the bevel from the theme’s colors.',\n 'wb.doctor.css-hide.found': 'This rule hides something with display: none.',\n 'wb.doctor.css-hide.fix': 'Use visible on a part under components (avatars, counts, names) or a layout variant such as sidebar: hidden; the owner can then switch it back.',\n 'wb.doctor.css-color.found': 'This rule uses a literal color.',\n 'wb.doctor.css-color.fix': 'Name a role (text, muted, accent, surface…) in colors or in a component, so the contrast pass covers it.',\n 'wb.doctor.css-font.found': 'This rule sets a font family.',\n 'wb.doctor.css-font.fix': 'Use font.roles: it sets the post, heading, numbers and mono faces without CSS.',\n 'wb.doctor.css-has.found': 'This rule uses :has(), which is matched again on every change of the page.',\n 'wb.doctor.css-has.fix': 'Keep :has() off rules that repeat down a timeline, or anchor the rule on a part.',\n 'wb.doctor.css-universal.found': 'This rule uses a universal selector (*), matched against every element.',\n 'wb.doctor.css-universal.fix': 'Anchor the rule on a part instead.',\n 'wb.doctor.css-blur.found': 'This rule blurs what is behind it (backdrop-filter), which is costly on parts that repeat.',\n 'wb.doctor.css-blur.fix': 'Use the glass fill of bars and panels: it is the cheaper place for blur.',\n 'wb.doctor.css-outline-none.found': 'This rule removes the focus outline, so keyboard focus is no longer visible.',\n 'wb.doctor.css-outline-none.fix': 'Use decor.focusRing to draw a visible focus ring instead.',\n 'wb.doctor.css-motion.found': 'This rule animates without checking for reduced motion.',\n 'wb.doctor.css-motion.fix': 'Keep motion inside @media (prefers-reduced-motion: no-preference), or let decor.motion do it for you.',\n 'wb.doctor.css-size.found': 'The CSS is more than 20,000 characters long.',\n 'wb.doctor.css-size.fix': 'The site recalculates styles with every rule; tokens and component settings compile to fewer rules.',\n 'wb.doctor.contrast.found': 'The {token} color was adjusted to stay readable ({ratio}:1 on the background).',\n 'wb.doctor.contrast.fix': 'Pick a lighter or darker {token} color; until then the theme is shown with the adjusted one.',\n 'wb.doctor.glass-post.found': 'Glass is set on posts, but it is not drawn: blur on every post of a timeline is too costly.',\n 'wb.doctor.glass-post.fix': 'Use a solid or tinted fill for posts and keep glass for bars and panels.',\n 'wb.doctor.small-post-text.found': 'Post text is {size}px, which is hard to read for many people.',\n 'wb.doctor.small-post-text.fix': 'Use 14px or more for the post text size, or raise the text size of the whole theme.',\n 'wb.doctor.nav-moved.found': 'The navigation is moved to the {position}, which changes where people look for it.',\n 'wb.doctor.nav-moved.fix': 'It only applies on wide panes, and the owner can turn layout changes off. Keep the default position unless the layout needs it.',\n} as const;\n\nconst fill = (text: string, params: Record<string, string>) => text.replace(/\\{(\\w+)\\}/g, (_, name: string) => params[name] ?? '');\n\ntype Rule = { id: string; kind: DoctorAdvice['kind']; test: RegExp; property: string };\n/** What a theme's free-form CSS does that a v2 group does without CSS. */\nconst CSS_RULES: Rule[] = [\n { id: 'css-radius', kind: 'lighter', test: /border-radius\\s*:/gi, property: 'border-radius' },\n { id: 'css-bevel', kind: 'lighter', test: /box-shadow\\s*:[^;{}]*inset[^;{}]*inset[^;{}]*inset/gi, property: 'box-shadow' },\n { id: 'css-hide', kind: 'lighter', test: /display\\s*:\\s*none/gi, property: 'display' },\n { id: 'css-color', kind: 'lighter', test: /(?:^|[;{\\s])(?:color|background(?:-color)?)\\s*:\\s*#[0-9a-f]{3,8}/gi, property: 'color' },\n { id: 'css-font', kind: 'lighter', test: /font-family\\s*:/gi, property: 'font-family' },\n { id: 'css-has', kind: 'performance', test: /:has\\(/gi, property: ':has()' },\n { id: 'css-universal', kind: 'performance', test: /(?:^|[\\s,>+~])\\*\\s*(?:[,{>+~:[]|$)/gm, property: '*' },\n { id: 'css-blur', kind: 'performance', test: /backdrop-filter\\s*:/gi, property: 'backdrop-filter' },\n { id: 'css-outline-none', kind: 'accessibility', test: /outline\\s*:\\s*(?:none|0)\\b/gi, property: 'outline' },\n { id: 'css-motion', kind: 'accessibility', test: /(?:animation|transition)\\s*:/gi, property: 'transition' },\n];\n\nconst MAX_PER_RULE = 25;\n\n/** The selector of the rule block a position of `css` is in (the text before its `{`), shortened. */\nfunction selectorAt(css: string, index: number): string {\n const open = css.lastIndexOf('{', index);\n if (open < 0) return '';\n const close = Math.max(css.lastIndexOf('}', open - 1), css.lastIndexOf(';', open - 1), css.lastIndexOf('{', open - 1));\n const selector = css.slice(close + 1, open).replace(/\\s+/g, ' ').trim();\n return selector.length > 80 ? `${selector.slice(0, 79)}…` : selector;\n}\n\nfunction advice(id: string, kind: DoctorAdvice['kind'], where: string, params: Record<string, string> = {}, key = id): DoctorAdvice {\n const words = DOCTOR_MESSAGES as Record<string, string>;\n return { kind, id, key, where, params, text: fill(words[`wb.doctor.${key}.found`]!, params), fix: fill(words[`wb.doctor.${key}.fix`]!, params) };\n}\n\n/** Advice for a theme document: every finding, with where it is. Empty when there is nothing to say. */\nexport function themeDoctor(theme: ThemeSpec): DoctorAdvice[] {\n const out: DoctorAdvice[] = [];\n const css = theme.css ?? '';\n if (css.trim()) {\n for (const rule of CSS_RULES) {\n const seen = new Set<string>();\n for (const match of css.matchAll(rule.test)) {\n const selector = selectorAt(css, match.index ?? 0) || 'css';\n const where = rule.id === 'css-universal' || rule.id === 'css-has' ? selector : `${selector} { ${rule.property} }`;\n if (seen.has(where)) continue;\n seen.add(where);\n out.push(advice(rule.id, rule.kind, where));\n if (seen.size >= MAX_PER_RULE) break;\n }\n }\n if (css.length > 20_000) out.push(advice('css-size', 'performance', 'css'));\n }\n if (theme.colors) {\n const fixed = ensureContrast(theme.colors);\n for (const role of ['text', 'muted', 'accent'] as const) {\n if (fixed[role] !== theme.colors[role]) {\n out.push(advice(`contrast-${role}`, 'contrast', `colors.${role}`, { token: role, ratio: contrastRatio(theme.colors[role], theme.colors.bg).toFixed(2) }, 'contrast'));\n }\n }\n }\n if (theme.components) {\n if (theme.components.post?.fill === 'glass') out.push(advice('glass-post', 'performance', 'components.post.fill'));\n const size = theme.components.postText?.fontSize;\n if (size !== undefined && size < 14) out.push(advice('small-post-text', 'accessibility', 'components.postText.fontSize', { size: String(size) }));\n }\n if (theme.variants?.nav === 'top' || theme.variants?.nav === 'bottom') {\n out.push(advice('nav-moved', 'accessibility', 'variants.nav', { position: theme.variants.nav }));\n }\n return out;\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /** The viewport box popovers must stay in. */
2
+ export type Box = {
3
+ left: number;
4
+ top: number;
5
+ right: number;
6
+ bottom: number;
7
+ };
8
+ export type Rect = {
9
+ left: number;
10
+ top: number;
11
+ width: number;
12
+ height: number;
13
+ };
14
+ /** Attribute on a shifted layer (a static rule hides its caret, which points at the old anchor). */
15
+ export declare const CLAMPED_ATTR = "data-wb-clamped";
16
+ export declare const CLAMP_X = "--wb-clamp-x";
17
+ export declare const CLAMP_Y = "--wb-clamp-y";
18
+ /**
19
+ * Pure: the shift that brings `rect` inside `box`. A rect larger than the box keeps its start edge
20
+ * inside (left/top) and reports `overflowY`, so the caller can cap its height and let it scroll.
21
+ */
22
+ export declare function clampShift(rect: Rect, box: Box): {
23
+ dx: number;
24
+ dy: number;
25
+ overflowY: boolean;
26
+ };
27
+ /** The box: the viewport less `margin` on every side and the bar insets top and bottom. */
28
+ export declare function viewportBox(width: number, height: number, insets: {
29
+ top: number;
30
+ bottom: number;
31
+ }, margin?: number): Box;
32
+ /** The static rules the guard relies on (part of the theme sheet). */
33
+ export declare function layerGuardCss(): string;
34
+ export type LayerGuardOptions = {
35
+ /** The container the site renders its portals into (X: `#layers`). */
36
+ root: string;
37
+ /** Inside a new layer, the element that is positioned and should be shifted (first match wins). */
38
+ targets: string[];
39
+ /** Bar insets in px (read from the theme's `--wb-bar-inset-top/bottom` by default). */
40
+ insets?: () => {
41
+ top: number;
42
+ bottom: number;
43
+ };
44
+ };
45
+ /** Watches the layer root; returns a stop function. Off in safe mode (the host never installs it then). */
46
+ export declare function installLayerGuard(win: Window, options: LayerGuardOptions): () => void;
47
+ export declare const VOID_ATTR = "data-wb-void";
48
+ /** Elements that make a part non-empty when they render. */
49
+ export declare const VISIBLE_INSIDE = "a,button,h2,img,svg,input,video,[role=\"heading\"],[data-testid=\"tweetText\"]";
50
+ /** The static rule the marker relies on. */
51
+ export declare const frameMarkerCss: () => string;
52
+ export type FrameMarkerOptions = {
53
+ /** Containers to watch (X: the sidebar column). */
54
+ scopes: string[];
55
+ /** Framed parts inside them (one selector list). */
56
+ parts: string;
57
+ };
58
+ /** Pure: whether `part` renders nothing (every candidate inside has no client rect). */
59
+ export declare function isVoid(part: Element): boolean;
60
+ export declare function installFrameMarker(win: Window, options: FrameMarkerOptions): () => void;
@@ -0,0 +1,235 @@
1
+ // Theme engine v3, step 8 (.omc/plans/theme-engine-v3.md 3.5, 4.2): the layer guard. A site positions
2
+ // its popovers (menus, hover cards, tooltips) from an anchor; a theme that moves the anchor (a bottom
3
+ // taskbar, a top bar) moves them past the window edge. Static CSS cannot see where a portal lands, so
4
+ // this guard measures each new layer root once and shifts it back inside the viewport minus the bar
5
+ // insets. Bounded: it watches only the layer container's children (no subtree attribute observation
6
+ // of the page), measures in an idle callback, at most a few layers per frame, never timeline cells.
7
+ /** Attribute on a shifted layer (a static rule hides its caret, which points at the old anchor). */
8
+ export const CLAMPED_ATTR = 'data-wb-clamped';
9
+ export const CLAMP_X = '--wb-clamp-x';
10
+ export const CLAMP_Y = '--wb-clamp-y';
11
+ /**
12
+ * Pure: the shift that brings `rect` inside `box`. A rect larger than the box keeps its start edge
13
+ * inside (left/top) and reports `overflowY`, so the caller can cap its height and let it scroll.
14
+ */
15
+ export function clampShift(rect, box) {
16
+ const shift = (start, size, min, max) => {
17
+ if (size >= max - min)
18
+ return min - start;
19
+ if (start < min)
20
+ return min - start;
21
+ if (start + size > max)
22
+ return max - (start + size);
23
+ return 0;
24
+ };
25
+ return {
26
+ dx: Math.round(shift(rect.left, rect.width, box.left, box.right)),
27
+ dy: Math.round(shift(rect.top, rect.height, box.top, box.bottom)),
28
+ overflowY: rect.height > box.bottom - box.top,
29
+ };
30
+ }
31
+ /** The box: the viewport less `margin` on every side and the bar insets top and bottom. */
32
+ export function viewportBox(width, height, insets, margin = 8) {
33
+ return { left: margin, top: margin + insets.top, right: width - margin, bottom: height - margin - insets.bottom };
34
+ }
35
+ /** The static rules the guard relies on (part of the theme sheet). */
36
+ export function layerGuardCss() {
37
+ return `[${CLAMPED_ATTR}]{translate:var(${CLAMP_X},0px) var(${CLAMP_Y},0px) !important}\n[${CLAMPED_ATTR}="scroll"]{max-height:var(--wb-clamp-h) !important;overflow-y:auto !important}`;
38
+ }
39
+ const px = (value) => { const n = parseFloat(value); return Number.isFinite(n) ? n : 0; };
40
+ /** Watches the layer root; returns a stop function. Off in safe mode (the host never installs it then). */
41
+ export function installLayerGuard(win, options) {
42
+ const doc = win.document;
43
+ const insets = options.insets ?? (() => {
44
+ const style = win.getComputedStyle(doc.documentElement);
45
+ return { top: px(style.getPropertyValue('--wb-bar-inset-top')), bottom: px(style.getPropertyValue('--wb-bar-inset-bottom')) };
46
+ });
47
+ const selector = options.targets.join(',');
48
+ const pending = new Set();
49
+ let scheduled = false;
50
+ const measure = () => {
51
+ scheduled = false;
52
+ let budget = 4;
53
+ for (const layer of [...pending]) {
54
+ if (budget-- <= 0)
55
+ break;
56
+ pending.delete(layer);
57
+ if (!layer.isConnected)
58
+ continue;
59
+ for (const target of layer.matches(selector) ? [layer] : [...layer.querySelectorAll(selector)].slice(0, 2))
60
+ clamp(target);
61
+ }
62
+ if (pending.size)
63
+ schedule();
64
+ };
65
+ // A measured popover is watched for the site moving it again (its inline position): only its own
66
+ // style attribute, and only position changes count (our own clamp variables change it too).
67
+ const placed = new WeakMap();
68
+ const position = (el) => `${el.style.left}|${el.style.top}|${el.style.right}|${el.style.bottom}|${el.style.transform}`;
69
+ const Observer = win.MutationObserver;
70
+ const moved = new Observer((records) => {
71
+ for (const record of records) {
72
+ const el = record.target;
73
+ if (el.isConnected && placed.get(el) !== position(el)) {
74
+ pending.add(el);
75
+ schedule();
76
+ }
77
+ }
78
+ });
79
+ // A popover that grows (a theme's font, content loading in) is measured again too.
80
+ const Resize = win.ResizeObserver;
81
+ const sized = Resize ? new Resize(entries => { for (const entry of entries) {
82
+ const el = entry.target;
83
+ if (el.isConnected) {
84
+ pending.add(el);
85
+ schedule();
86
+ }
87
+ } }) : null;
88
+ const clamp = (el) => {
89
+ if (!placed.has(el)) {
90
+ moved.observe(el, { attributes: true, attributeFilter: ['style'] });
91
+ sized?.observe(el);
92
+ }
93
+ placed.set(el, position(el));
94
+ // Measure without our own shift.
95
+ el.style.removeProperty(CLAMP_X);
96
+ el.style.removeProperty(CLAMP_Y);
97
+ const r = el.getBoundingClientRect();
98
+ if (r.width < 1 || r.height < 1)
99
+ return;
100
+ const box = viewportBox(win.innerWidth, win.innerHeight, insets());
101
+ const { dx, dy, overflowY } = clampShift({ left: r.left, top: r.top, width: r.width, height: r.height }, box);
102
+ if (!dx && !dy && !overflowY) {
103
+ el.removeAttribute(CLAMPED_ATTR);
104
+ return;
105
+ }
106
+ el.style.setProperty(CLAMP_X, `${dx}px`);
107
+ el.style.setProperty(CLAMP_Y, `${dy}px`);
108
+ if (overflowY)
109
+ el.style.setProperty('--wb-clamp-h', `${Math.max(80, box.bottom - box.top)}px`);
110
+ el.setAttribute(CLAMPED_ATTR, overflowY ? 'scroll' : '');
111
+ };
112
+ const schedule = () => {
113
+ if (scheduled)
114
+ return;
115
+ scheduled = true;
116
+ const idle = win.requestIdleCallback;
117
+ if (idle)
118
+ idle.call(win, measure, { timeout: 120 });
119
+ else
120
+ win.setTimeout(measure, 16);
121
+ };
122
+ // Only a node that is or holds a popover is worth a measurement (typing in a dialog inserts nodes too).
123
+ const queue = (node) => {
124
+ if (!node || node.nodeType !== 1)
125
+ return;
126
+ const el = node;
127
+ if (!el.matches(selector) && !el.querySelector(selector))
128
+ return;
129
+ pending.add(el);
130
+ schedule();
131
+ // The site positions a portal after it mounts (a layout effect, a measured anchor): measure again
132
+ // once it has settled. Two late passes, no observer on the page's style attributes.
133
+ for (const delay of [120, 450])
134
+ win.setTimeout(() => { if (el.isConnected) {
135
+ pending.add(el);
136
+ schedule();
137
+ } }, delay);
138
+ };
139
+ let observer = null;
140
+ let layers = null;
141
+ const attach = () => {
142
+ const found = doc.querySelector(options.root);
143
+ if (found === layers)
144
+ return;
145
+ observer?.disconnect();
146
+ layers = found;
147
+ if (!found)
148
+ return;
149
+ // Children of the layer root and their children: a portal mounts its positioned box a level or two down.
150
+ const next = new Observer((records) => { for (const record of records)
151
+ for (const node of record.addedNodes)
152
+ queue(node.nodeType === 1 ? (node.closest(selector) ?? node) : null); });
153
+ next.observe(found, { childList: true, subtree: true });
154
+ observer = next;
155
+ for (const child of found.children)
156
+ queue(child);
157
+ };
158
+ attach();
159
+ const retry = win.setInterval(attach, 2000);
160
+ const onResize = () => { if (layers)
161
+ for (const child of layers.children)
162
+ queue(child); };
163
+ win.addEventListener('resize', onResize);
164
+ return () => { observer?.disconnect(); moved.disconnect(); sized?.disconnect(); win.clearInterval(retry); win.removeEventListener('resize', onResize); };
165
+ }
166
+ // ---------------------------------------------------------------------------------------------
167
+ // Frame marker (theme engine v3 4.3, step 10): a framed part (a card, a panel) whose content is all
168
+ // hidden (a feature switch, a plugin, the site itself) collapses instead of leaving an empty frame.
169
+ // Static CSS cannot see `display:none` inside, so the marker sets `data-wb-void` and a static rule
170
+ // hides the part. Watched by a ResizeObserver per framed part (a hidden module changes the part's size)
171
+ // and a child-list observer on the scopes (new parts), never by a timer.
172
+ export const VOID_ATTR = 'data-wb-void';
173
+ /** Elements that make a part non-empty when they render. */
174
+ export const VISIBLE_INSIDE = 'a,button,h2,img,svg,input,video,[role="heading"],[data-testid="tweetText"]';
175
+ /** The static rule the marker relies on. */
176
+ export const frameMarkerCss = () => `:root:root [${VOID_ATTR}]{display:none !important}`;
177
+ /** Pure: whether `part` renders nothing (every candidate inside has no client rect). */
178
+ export function isVoid(part) {
179
+ return ![...part.querySelectorAll(VISIBLE_INSIDE)].some(el => el.getClientRects().length > 0);
180
+ }
181
+ export function installFrameMarker(win, options) {
182
+ const doc = win.document;
183
+ const Resize = win.ResizeObserver;
184
+ const Observer = win.MutationObserver;
185
+ const watched = new WeakSet();
186
+ let frame = 0;
187
+ const check = (part) => {
188
+ // A void part is display:none itself: lift the mark to see whether something came back.
189
+ const was = part.hasAttribute(VOID_ATTR);
190
+ if (was)
191
+ part.removeAttribute(VOID_ATTR);
192
+ if (isVoid(part))
193
+ part.setAttribute(VOID_ATTR, '');
194
+ };
195
+ const sweep = () => {
196
+ frame = 0;
197
+ for (const scope of options.scopes) {
198
+ const root = doc.querySelector(scope);
199
+ if (!root)
200
+ continue;
201
+ for (const part of root.querySelectorAll(options.parts)) {
202
+ if (!watched.has(part)) {
203
+ watched.add(part);
204
+ sized?.observe(part);
205
+ }
206
+ check(part);
207
+ }
208
+ }
209
+ };
210
+ const schedule = () => { if (!frame)
211
+ frame = win.requestAnimationFrame(sweep); };
212
+ const sized = Resize ? new Resize(schedule) : null;
213
+ // Inner modules change while the part keeps its size only when hidden content was already 0 tall:
214
+ // child-list changes under the scopes cover those, and new parts.
215
+ const added = new Observer(schedule);
216
+ const roots = new WeakSet();
217
+ const attach = () => {
218
+ for (const scope of options.scopes) {
219
+ const root = doc.querySelector(scope);
220
+ if (root && !roots.has(root)) {
221
+ roots.add(root);
222
+ added.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] });
223
+ schedule();
224
+ }
225
+ }
226
+ };
227
+ attach();
228
+ // Scopes mount late and are replaced on navigation: looked up again every 2 s (one querySelector per
229
+ // scope; the per-part work is all observer-driven).
230
+ const retry = win.setInterval(attach, 2000);
231
+ return () => { sized?.disconnect(); added.disconnect(); win.clearInterval(retry); if (frame)
232
+ win.cancelAnimationFrame(frame); for (const el of doc.querySelectorAll(`[${VOID_ATTR}]`))
233
+ el.removeAttribute(VOID_ATTR); };
234
+ }
235
+ //# sourceMappingURL=themeGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themeGuards.js","sourceRoot":"","sources":["../../../runtime/src/themeGuards.ts"],"names":[],"mappings":"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","sourcesContent":["// Theme engine v3, step 8 (.omc/plans/theme-engine-v3.md 3.5, 4.2): the layer guard. A site positions\n// its popovers (menus, hover cards, tooltips) from an anchor; a theme that moves the anchor (a bottom\n// taskbar, a top bar) moves them past the window edge. Static CSS cannot see where a portal lands, so\n// this guard measures each new layer root once and shifts it back inside the viewport minus the bar\n// insets. Bounded: it watches only the layer container's children (no subtree attribute observation\n// of the page), measures in an idle callback, at most a few layers per frame, never timeline cells.\n\n/** The viewport box popovers must stay in. */\nexport type Box = { left: number; top: number; right: number; bottom: number };\nexport type Rect = { left: number; top: number; width: number; height: number };\n\n/** Attribute on a shifted layer (a static rule hides its caret, which points at the old anchor). */\nexport const CLAMPED_ATTR = 'data-wb-clamped';\nexport const CLAMP_X = '--wb-clamp-x';\nexport const CLAMP_Y = '--wb-clamp-y';\n\n/**\n * Pure: the shift that brings `rect` inside `box`. A rect larger than the box keeps its start edge\n * inside (left/top) and reports `overflowY`, so the caller can cap its height and let it scroll.\n */\nexport function clampShift(rect: Rect, box: Box): { dx: number; dy: number; overflowY: boolean } {\n const shift = (start: number, size: number, min: number, max: number) => {\n if (size >= max - min) return min - start;\n if (start < min) return min - start;\n if (start + size > max) return max - (start + size);\n return 0;\n };\n return {\n dx: Math.round(shift(rect.left, rect.width, box.left, box.right)),\n dy: Math.round(shift(rect.top, rect.height, box.top, box.bottom)),\n overflowY: rect.height > box.bottom - box.top,\n };\n}\n\n/** The box: the viewport less `margin` on every side and the bar insets top and bottom. */\nexport function viewportBox(width: number, height: number, insets: { top: number; bottom: number }, margin = 8): Box {\n return { left: margin, top: margin + insets.top, right: width - margin, bottom: height - margin - insets.bottom };\n}\n\n/** The static rules the guard relies on (part of the theme sheet). */\nexport function layerGuardCss(): string {\n return `[${CLAMPED_ATTR}]{translate:var(${CLAMP_X},0px) var(${CLAMP_Y},0px) !important}\\n[${CLAMPED_ATTR}=\"scroll\"]{max-height:var(--wb-clamp-h) !important;overflow-y:auto !important}`;\n}\n\nexport type LayerGuardOptions = {\n /** The container the site renders its portals into (X: `#layers`). */\n root: string;\n /** Inside a new layer, the element that is positioned and should be shifted (first match wins). */\n targets: string[];\n /** Bar insets in px (read from the theme's `--wb-bar-inset-top/bottom` by default). */\n insets?: () => { top: number; bottom: number };\n};\n\nconst px = (value: string) => { const n = parseFloat(value); return Number.isFinite(n) ? n : 0; };\n\n/** Watches the layer root; returns a stop function. Off in safe mode (the host never installs it then). */\nexport function installLayerGuard(win: Window, options: LayerGuardOptions): () => void {\n const doc = win.document;\n const insets = options.insets ?? (() => {\n const style = win.getComputedStyle(doc.documentElement);\n return { top: px(style.getPropertyValue('--wb-bar-inset-top')), bottom: px(style.getPropertyValue('--wb-bar-inset-bottom')) };\n });\n const selector = options.targets.join(',');\n const pending = new Set<Element>();\n let scheduled = false;\n const measure = () => {\n scheduled = false;\n let budget = 4;\n for (const layer of [...pending]) {\n if (budget-- <= 0) break;\n pending.delete(layer);\n if (!layer.isConnected) continue;\n for (const target of layer.matches(selector) ? [layer] : [...layer.querySelectorAll(selector)].slice(0, 2)) clamp(target as HTMLElement);\n }\n if (pending.size) schedule();\n };\n // A measured popover is watched for the site moving it again (its inline position): only its own\n // style attribute, and only position changes count (our own clamp variables change it too).\n const placed = new WeakMap<HTMLElement, string>();\n const position = (el: HTMLElement) => `${el.style.left}|${el.style.top}|${el.style.right}|${el.style.bottom}|${el.style.transform}`;\n const Observer = (win as Window & { MutationObserver: typeof MutationObserver }).MutationObserver;\n const moved = new Observer((records: MutationRecord[]) => {\n for (const record of records) {\n const el = record.target as HTMLElement;\n if (el.isConnected && placed.get(el) !== position(el)) { pending.add(el); schedule(); }\n }\n });\n // A popover that grows (a theme's font, content loading in) is measured again too.\n const Resize = (win as Window & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver;\n const sized = Resize ? new Resize(entries => { for (const entry of entries) { const el = entry.target as HTMLElement; if (el.isConnected) { pending.add(el); schedule(); } } }) : null;\n const clamp = (el: HTMLElement) => {\n if (!placed.has(el)) { moved.observe(el, { attributes: true, attributeFilter: ['style'] }); sized?.observe(el); }\n placed.set(el, position(el));\n // Measure without our own shift.\n el.style.removeProperty(CLAMP_X); el.style.removeProperty(CLAMP_Y);\n const r = el.getBoundingClientRect();\n if (r.width < 1 || r.height < 1) return;\n const box = viewportBox(win.innerWidth, win.innerHeight, insets());\n const { dx, dy, overflowY } = clampShift({ left: r.left, top: r.top, width: r.width, height: r.height }, box);\n if (!dx && !dy && !overflowY) { el.removeAttribute(CLAMPED_ATTR); return; }\n el.style.setProperty(CLAMP_X, `${dx}px`); el.style.setProperty(CLAMP_Y, `${dy}px`);\n if (overflowY) el.style.setProperty('--wb-clamp-h', `${Math.max(80, box.bottom - box.top)}px`);\n el.setAttribute(CLAMPED_ATTR, overflowY ? 'scroll' : '');\n };\n const schedule = () => {\n if (scheduled) return;\n scheduled = true;\n const idle = (win as Window & { requestIdleCallback?: (cb: () => void, o?: { timeout: number }) => number }).requestIdleCallback;\n if (idle) idle.call(win, measure, { timeout: 120 }); else win.setTimeout(measure, 16);\n };\n // Only a node that is or holds a popover is worth a measurement (typing in a dialog inserts nodes too).\n const queue = (node: Node | null) => {\n if (!node || node.nodeType !== 1) return;\n const el = node as Element;\n if (!el.matches(selector) && !el.querySelector(selector)) return;\n pending.add(el); schedule();\n // The site positions a portal after it mounts (a layout effect, a measured anchor): measure again\n // once it has settled. Two late passes, no observer on the page's style attributes.\n for (const delay of [120, 450]) win.setTimeout(() => { if (el.isConnected) { pending.add(el); schedule(); } }, delay);\n };\n let observer: MutationObserver | null = null;\n let layers: Element | null = null;\n const attach = () => {\n const found = doc.querySelector(options.root);\n if (found === layers) return;\n observer?.disconnect();\n layers = found;\n if (!found) return;\n // Children of the layer root and their children: a portal mounts its positioned box a level or two down.\n const next = new Observer((records: MutationRecord[]) => { for (const record of records) for (const node of record.addedNodes) queue(node.nodeType === 1 ? ((node as Element).closest(selector) ?? node) : null); });\n next.observe(found, { childList: true, subtree: true });\n observer = next;\n for (const child of found.children) queue(child);\n };\n attach();\n const retry = win.setInterval(attach, 2000);\n const onResize = () => { if (layers) for (const child of layers.children) queue(child); };\n win.addEventListener('resize', onResize);\n return () => { observer?.disconnect(); moved.disconnect(); sized?.disconnect(); win.clearInterval(retry); win.removeEventListener('resize', onResize); };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Frame marker (theme engine v3 4.3, step 10): a framed part (a card, a panel) whose content is all\n// hidden (a feature switch, a plugin, the site itself) collapses instead of leaving an empty frame.\n// Static CSS cannot see `display:none` inside, so the marker sets `data-wb-void` and a static rule\n// hides the part. Watched by a ResizeObserver per framed part (a hidden module changes the part's size)\n// and a child-list observer on the scopes (new parts), never by a timer.\n\nexport const VOID_ATTR = 'data-wb-void';\n/** Elements that make a part non-empty when they render. */\nexport const VISIBLE_INSIDE = 'a,button,h2,img,svg,input,video,[role=\"heading\"],[data-testid=\"tweetText\"]';\n\n/** The static rule the marker relies on. */\nexport const frameMarkerCss = () => `:root:root [${VOID_ATTR}]{display:none !important}`;\n\nexport type FrameMarkerOptions = {\n /** Containers to watch (X: the sidebar column). */\n scopes: string[];\n /** Framed parts inside them (one selector list). */\n parts: string;\n};\n\n/** Pure: whether `part` renders nothing (every candidate inside has no client rect). */\nexport function isVoid(part: Element): boolean {\n return ![...part.querySelectorAll(VISIBLE_INSIDE)].some(el => el.getClientRects().length > 0);\n}\n\nexport function installFrameMarker(win: Window, options: FrameMarkerOptions): () => void {\n const doc = win.document;\n const Resize = (win as Window & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver;\n const Observer = (win as Window & { MutationObserver: typeof MutationObserver }).MutationObserver;\n const watched = new WeakSet<Element>();\n let frame = 0;\n const check = (part: Element) => {\n // A void part is display:none itself: lift the mark to see whether something came back.\n const was = part.hasAttribute(VOID_ATTR);\n if (was) part.removeAttribute(VOID_ATTR);\n if (isVoid(part)) part.setAttribute(VOID_ATTR, '');\n };\n const sweep = () => {\n frame = 0;\n for (const scope of options.scopes) {\n const root = doc.querySelector(scope);\n if (!root) continue;\n for (const part of root.querySelectorAll(options.parts)) {\n if (!watched.has(part)) { watched.add(part); sized?.observe(part); }\n check(part);\n }\n }\n };\n const schedule = () => { if (!frame) frame = win.requestAnimationFrame(sweep); };\n const sized = Resize ? new Resize(schedule) : null;\n // Inner modules change while the part keeps its size only when hidden content was already 0 tall:\n // child-list changes under the scopes cover those, and new parts.\n const added = new Observer(schedule);\n const roots = new WeakSet<Element>();\n const attach = () => {\n for (const scope of options.scopes) {\n const root = doc.querySelector(scope);\n if (root && !roots.has(root)) { roots.add(root); added.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] }); schedule(); }\n }\n };\n attach();\n // Scopes mount late and are replaced on navigation: looked up again every 2 s (one querySelector per\n // scope; the per-part work is all observer-driven).\n const retry = win.setInterval(attach, 2000);\n return () => { sized?.disconnect(); added.disconnect(); win.clearInterval(retry); if (frame) win.cancelAnimationFrame(frame); for (const el of doc.querySelectorAll(`[${VOID_ATTR}]`)) el.removeAttribute(VOID_ATTR); };\n}\n"]}
@@ -0,0 +1,130 @@
1
+ import { type ColorRole, type RoleColors } from './themeComponents.ts';
2
+ /** Semantic names a pack may draw. `.active` is a selected navigation item, `.on` an action that is set. */
3
+ export declare const ICON_NAMES: readonly ["home", "home.active", "explore", "explore.active", "notifications", "notifications.active", "messages", "messages.active", "grok", "bookmarks", "bookmarks.active", "lists", "communities", "premium", "profile", "profile.active", "more", "compose", "reply", "repost", "repost.on", "like", "like.on", "views", "bookmark", "bookmark.on", "share", "verified", "logo", "caret", "back", "close", "search", "settings", "media", "gif", "poll", "emoji", "schedule", "location", "pinned", "repostContext", "lock", "history", "studio", "articles"];
4
+ export type IconName = typeof ICON_NAMES[number];
5
+ export declare const ICON_GROUPS: readonly ["nav", "actions", "composer", "misc"];
6
+ export type IconGroup = typeof ICON_GROUPS[number];
7
+ /** Limits of a pack (the JSON Schema and the Store check say the same). */
8
+ export declare const ICON_LIMITS: {
9
+ readonly names: 64;
10
+ readonly pathChars: 4000;
11
+ readonly layers: 6;
12
+ readonly pngChars: 6000;
13
+ readonly packBytes: 120000;
14
+ readonly labelChars: 12;
15
+ readonly overrides: 8;
16
+ };
17
+ /** Path data only: commands, numbers, separators. */
18
+ export declare const PATH_GRAMMAR: RegExp;
19
+ export type IconFill = ColorRole | string | {
20
+ from: string;
21
+ to: string;
22
+ };
23
+ export type IconGlyph = {
24
+ d: string;
25
+ rule?: 'evenodd';
26
+ stroke?: boolean;
27
+ } | {
28
+ layers: Array<{
29
+ d: string;
30
+ fill: IconFill;
31
+ opacity?: number;
32
+ rule?: 'evenodd';
33
+ }>;
34
+ } | {
35
+ png: string;
36
+ pixel?: boolean;
37
+ };
38
+ export type IconStyle = {
39
+ stroke: number;
40
+ cap: 'round' | 'square' | 'butt';
41
+ join: 'round' | 'miter' | 'bevel';
42
+ scale: number;
43
+ };
44
+ export declare const BASE_ICON_STYLE: IconStyle;
45
+ export declare const ICON_LABEL_NAMES: readonly ["reply", "repost", "like", "views", "bookmark", "share"];
46
+ export type IconLabels = Partial<Record<typeof ICON_LABEL_NAMES[number], string>>;
47
+ export type IconSymbols = {
48
+ like?: 'heart' | 'star' | 'custom';
49
+ separator?: 'dot' | 'bullet' | 'slash' | 'dash' | 'pipe' | 'none';
50
+ };
51
+ export type IconPack = {
52
+ id: string;
53
+ name: string;
54
+ description: string;
55
+ author: string;
56
+ /** SPDX id, or `own` for the author's own drawings, or a plain name for artwork under other terms. */
57
+ license: string;
58
+ /** Where the drawings come from (a project name or URL as text) and whom to credit. */
59
+ source: string;
60
+ credits: string;
61
+ version: 1;
62
+ style: IconStyle;
63
+ glyphs: Partial<Record<IconName, IconGlyph>>;
64
+ symbols?: IconSymbols;
65
+ labels?: IconLabels;
66
+ };
67
+ /** What a theme says about icons: a pack by id, plus small adjustments. Never a whole pack. */
68
+ export type ThemeIcons = {
69
+ pack?: string;
70
+ style?: Partial<IconStyle>;
71
+ overrides?: Partial<Record<IconName, IconGlyph>>;
72
+ symbols?: IconSymbols;
73
+ labels?: IconLabels;
74
+ };
75
+ /** One glyph from JSON, or null when it is not valid path data, layers or a small PNG. */
76
+ export declare function sanitizeGlyph(value: unknown): IconGlyph | null;
77
+ export declare const sanitizeIconStyle: (value: unknown, base?: IconStyle) => IconStyle;
78
+ /** Any JSON to a valid pack, or null when it has no id, no name or no valid glyph. */
79
+ export declare function sanitizeIconPack(input: unknown): IconPack | null;
80
+ /** Parses an imported `*.icons.json`; errors are human-readable. */
81
+ export declare function parseIconPackJson(json: string): {
82
+ pack: IconPack;
83
+ warnings: string[];
84
+ } | {
85
+ error: string;
86
+ };
87
+ /** The `icons` group of a theme from JSON. Null when it says nothing valid. */
88
+ export declare function sanitizeThemeIcons(value: unknown): ThemeIcons | null;
89
+ /** `theme`: follow the theme's suggestion; `site`: the site's own icons; otherwise a pack id. */
90
+ export type IconChoice = 'theme' | 'site' | string;
91
+ export declare const normalizeIconChoice: (value: unknown) => IconChoice;
92
+ export type IconCatalog = {
93
+ packs: readonly IconPack[];
94
+ get(id: string | undefined): IconPack | null;
95
+ /** The pack that is drawn: the owner's pick, else the theme's suggestion; null for the site's own icons or an unknown pack. */
96
+ resolve(choice: IconChoice, theme: ThemeIcons | null | undefined): IconPack | null;
97
+ };
98
+ export declare function createIconCatalog(builtins: readonly IconPack[], installed?: readonly IconPack[]): IconCatalog;
99
+ /** The SVG document of a glyph, built from validated data. `colors` resolves role fills of layers; a single path is black (it is used as a mask). */
100
+ export declare function glyphSvg(glyph: IconGlyph, style: IconStyle, colors?: RoleColors | null): string | null;
101
+ /** True for glyphs that carry their own colors (drawn as an image, not as a mask of the current color). */
102
+ export declare const isColored: (glyph: IconGlyph) => boolean;
103
+ /** How a site finds one of its icons: the container that holds it (stable hooks), else the start of its path data. */
104
+ export type IconMatcher = {
105
+ /** Selector of the icon's `<svg>` by its container (`[data-testid="like"] svg`). */
106
+ ctx?: string;
107
+ /** Prefixes of the site's own path data for this icon, for icons without a container hook. */
108
+ sig?: readonly string[];
109
+ group: IconGroup;
110
+ /** Needed for its group to be drawn at all (a rail never shows half ours, half the site's). */
111
+ core?: boolean;
112
+ };
113
+ export type IconTable = Partial<Record<IconName, IconMatcher>>;
114
+ /** Selectors of the `<svg>` boxes of one icon. */
115
+ export declare function iconSelectors(matcher: IconMatcher): string[];
116
+ export type IconContext = {
117
+ colors: RoleColors | null;
118
+ /** Attribute on `<html>` that lists the groups that are on (`data-wb-icons="nav actions"`); absent: every group draws. */
119
+ gate?: string;
120
+ /** The owner lets themes and packs change icons (default: yes). */
121
+ allowed?: boolean;
122
+ };
123
+ /**
124
+ * CSS that draws `pack` over a site's icons. One rule per glyph the pack has and the site's table
125
+ * knows, inside guards: forced colors remove backgrounds (a masked icon would vanish) and an engine
126
+ * without masks would paint solid squares.
127
+ */
128
+ export declare function compileIcons(pack: IconPack | null, theme: ThemeIcons | null | undefined, table: IconTable, ctx: IconContext): string;
129
+ /** The groups of `table` whose core icons are all on the page (`has(selector)` says whether one matches). Empty table entries count as present. */
130
+ export declare function iconGroupsPresent(table: IconTable, has: (selector: string) => boolean): IconGroup[];