@overcrow/mcp 0.0.0-stage → 0.1.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 (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -2
  3. package/dist/audit/index.js +103 -0
  4. package/dist/audit/lexer.js +417 -0
  5. package/dist/audit/project.js +200 -0
  6. package/dist/audit/rules.js +661 -0
  7. package/dist/bootstrap/download.js +229 -0
  8. package/dist/bootstrap/install.js +280 -0
  9. package/dist/bootstrap/pin.js +85 -0
  10. package/dist/bootstrap/pin.json +119 -0
  11. package/dist/bootstrap/zip.js +258 -0
  12. package/dist/cli.js +200 -0
  13. package/dist/confine.js +244 -0
  14. package/dist/content/docs/en/cli.md +353 -0
  15. package/dist/content/docs/en/dev-channel.md +130 -0
  16. package/dist/content/docs/en/elements.md +469 -0
  17. package/dist/content/docs/en/forms.md +328 -0
  18. package/dist/content/docs/en/guide.md +253 -0
  19. package/dist/content/docs/en/index.md +85 -0
  20. package/dist/content/docs/en/limits.md +241 -0
  21. package/dist/content/docs/en/logic.md +446 -0
  22. package/dist/content/docs/en/manifest.md +381 -0
  23. package/dist/content/docs/en/mcp.md +165 -0
  24. package/dist/content/docs/en/package.md +131 -0
  25. package/dist/content/docs/en/publishing.md +162 -0
  26. package/dist/content/docs/en/reference.md +55 -0
  27. package/dist/content/docs/en/result-shapes.md +332 -0
  28. package/dist/content/docs/en/sdk.md +295 -0
  29. package/dist/content/docs/en/security.md +115 -0
  30. package/dist/content/docs/en/service-reference.md +344 -0
  31. package/dist/content/docs/en/services.md +517 -0
  32. package/dist/content/docs/en/style-properties.md +270 -0
  33. package/dist/content/docs/en/style.md +266 -0
  34. package/dist/content/docs/en/testing.md +400 -0
  35. package/dist/content/docs/en/view.md +338 -0
  36. package/dist/content/docs/en/widgets.md +76 -0
  37. package/dist/content/docs/fr/cli.md +367 -0
  38. package/dist/content/docs/fr/dev-channel.md +140 -0
  39. package/dist/content/docs/fr/elements.md +475 -0
  40. package/dist/content/docs/fr/forms.md +341 -0
  41. package/dist/content/docs/fr/guide.md +264 -0
  42. package/dist/content/docs/fr/index.md +90 -0
  43. package/dist/content/docs/fr/limits.md +244 -0
  44. package/dist/content/docs/fr/logic.md +461 -0
  45. package/dist/content/docs/fr/manifest.md +392 -0
  46. package/dist/content/docs/fr/mcp.md +173 -0
  47. package/dist/content/docs/fr/package.md +139 -0
  48. package/dist/content/docs/fr/publishing.md +172 -0
  49. package/dist/content/docs/fr/reference.md +59 -0
  50. package/dist/content/docs/fr/result-shapes.md +333 -0
  51. package/dist/content/docs/fr/sdk.md +301 -0
  52. package/dist/content/docs/fr/security.md +129 -0
  53. package/dist/content/docs/fr/service-reference.md +346 -0
  54. package/dist/content/docs/fr/services.md +540 -0
  55. package/dist/content/docs/fr/style-properties.md +272 -0
  56. package/dist/content/docs/fr/style.md +281 -0
  57. package/dist/content/docs/fr/testing.md +417 -0
  58. package/dist/content/docs/fr/view.md +352 -0
  59. package/dist/content/docs/fr/widgets.md +81 -0
  60. package/dist/content/examples/clock/LICENSE +21 -0
  61. package/dist/content/examples/clock/README.md +75 -0
  62. package/dist/content/examples/clock/listing.json +19 -0
  63. package/dist/content/examples/clock/locales/en.json +4 -0
  64. package/dist/content/examples/clock/locales/fr.json +4 -0
  65. package/dist/content/examples/clock/logic.ts +85 -0
  66. package/dist/content/examples/clock/manifest.json +44 -0
  67. package/dist/content/examples/clock/package.json +16 -0
  68. package/dist/content/examples/clock/style.ocss +26 -0
  69. package/dist/content/examples/clock/tests/hidden.scenario.json +68 -0
  70. package/dist/content/examples/clock/tests/load-logic.mjs +22 -0
  71. package/dist/content/examples/clock/tests/logic.test.mjs +89 -0
  72. package/dist/content/examples/clock/tests/options.scenario.json +108 -0
  73. package/dist/content/examples/clock/tests/preview.scenario.json +25 -0
  74. package/dist/content/examples/clock/tests/render.scenario.json +94 -0
  75. package/dist/content/examples/clock/tests/summer-time.scenario.json +74 -0
  76. package/dist/content/examples/clock/tests/ticks.scenario.json +120 -0
  77. package/dist/content/examples/clock/tsconfig.json +16 -0
  78. package/dist/content/examples/clock/view.ocml +8 -0
  79. package/dist/content/examples/countdown/LICENSE +20 -0
  80. package/dist/content/examples/countdown/listing.json +10 -0
  81. package/dist/content/examples/countdown/locales/en.json +15 -0
  82. package/dist/content/examples/countdown/locales/fr.json +15 -0
  83. package/dist/content/examples/countdown/logic.ts +179 -0
  84. package/dist/content/examples/countdown/manifest.json +34 -0
  85. package/dist/content/examples/countdown/package.json +15 -0
  86. package/dist/content/examples/countdown/style.ocss +94 -0
  87. package/dist/content/examples/countdown/tsconfig.json +16 -0
  88. package/dist/content/examples/countdown/view.ocml +42 -0
  89. package/dist/content/examples/fps/LICENSE +21 -0
  90. package/dist/content/examples/fps/README.md +64 -0
  91. package/dist/content/examples/fps/listing.json +19 -0
  92. package/dist/content/examples/fps/locales/en.json +11 -0
  93. package/dist/content/examples/fps/locales/fr.json +11 -0
  94. package/dist/content/examples/fps/logic.ts +64 -0
  95. package/dist/content/examples/fps/manifest.json +30 -0
  96. package/dist/content/examples/fps/package.json +16 -0
  97. package/dist/content/examples/fps/style.ocss +31 -0
  98. package/dist/content/examples/fps/tests/label.scenario.json +63 -0
  99. package/dist/content/examples/fps/tests/load-logic.mjs +22 -0
  100. package/dist/content/examples/fps/tests/logic.test.mjs +60 -0
  101. package/dist/content/examples/fps/tests/preview.scenario.json +29 -0
  102. package/dist/content/examples/fps/tests/refused.scenario.json +20 -0
  103. package/dist/content/examples/fps/tests/render.scenario.json +96 -0
  104. package/dist/content/examples/fps/tests/stale.scenario.json +91 -0
  105. package/dist/content/examples/fps/tests/unavailable.scenario.json +111 -0
  106. package/dist/content/examples/fps/tsconfig.json +16 -0
  107. package/dist/content/examples/fps/view.ocml +11 -0
  108. package/dist/content/examples/media/LICENSE +21 -0
  109. package/dist/content/examples/media/README.md +147 -0
  110. package/dist/content/examples/media/listing.json +19 -0
  111. package/dist/content/examples/media/locales/en.json +11 -0
  112. package/dist/content/examples/media/locales/fr.json +11 -0
  113. package/dist/content/examples/media/logic.ts +191 -0
  114. package/dist/content/examples/media/manifest.json +35 -0
  115. package/dist/content/examples/media/package.json +16 -0
  116. package/dist/content/examples/media/style.ocss +148 -0
  117. package/dist/content/examples/media/tests/covers.scenario.json +144 -0
  118. package/dist/content/examples/media/tests/gesture.scenario.json +173 -0
  119. package/dist/content/examples/media/tests/load-logic.mjs +22 -0
  120. package/dist/content/examples/media/tests/logic.test.mjs +176 -0
  121. package/dist/content/examples/media/tests/marquee.scenario.json +127 -0
  122. package/dist/content/examples/media/tests/preview.scenario.json +52 -0
  123. package/dist/content/examples/media/tests/read-only.scenario.json +53 -0
  124. package/dist/content/examples/media/tests/refused.scenario.json +22 -0
  125. package/dist/content/examples/media/tests/refused.test.mjs +31 -0
  126. package/dist/content/examples/media/tests/render.scenario.json +136 -0
  127. package/dist/content/examples/media/tests/states.scenario.json +257 -0
  128. package/dist/content/examples/media/tests/unavailable.scenario.json +120 -0
  129. package/dist/content/examples/media/tsconfig.json +16 -0
  130. package/dist/content/examples/media/view.ocml +41 -0
  131. package/dist/content/examples/notes/LICENSE +21 -0
  132. package/dist/content/examples/notes/README.md +219 -0
  133. package/dist/content/examples/notes/listing.json +19 -0
  134. package/dist/content/examples/notes/locales/en.json +25 -0
  135. package/dist/content/examples/notes/locales/fr.json +25 -0
  136. package/dist/content/examples/notes/logic.ts +799 -0
  137. package/dist/content/examples/notes/manifest.json +40 -0
  138. package/dist/content/examples/notes/package.json +16 -0
  139. package/dist/content/examples/notes/style.ocss +469 -0
  140. package/dist/content/examples/notes/tests/checks.scenario.json +237 -0
  141. package/dist/content/examples/notes/tests/create.scenario.json +260 -0
  142. package/dist/content/examples/notes/tests/delete.scenario.json +342 -0
  143. package/dist/content/examples/notes/tests/drafts.scenario.json +714 -0
  144. package/dist/content/examples/notes/tests/editor.scenario.json +232 -0
  145. package/dist/content/examples/notes/tests/frame.scenario.json +93 -0
  146. package/dist/content/examples/notes/tests/interactive.scenario.json +344 -0
  147. package/dist/content/examples/notes/tests/limits.scenario.json +1351 -0
  148. package/dist/content/examples/notes/tests/load-logic.mjs +22 -0
  149. package/dist/content/examples/notes/tests/logic.test.mjs +514 -0
  150. package/dist/content/examples/notes/tests/preview.scenario.json +99 -0
  151. package/dist/content/examples/notes/tests/read-only.scenario.json +105 -0
  152. package/dist/content/examples/notes/tests/refused.scenario.json +33 -0
  153. package/dist/content/examples/notes/tests/save.scenario.json +378 -0
  154. package/dist/content/examples/notes/tests/states.scenario.json +640 -0
  155. package/dist/content/examples/notes/tests/typing.scenario.json +562 -0
  156. package/dist/content/examples/notes/tests/unavailable.scenario.json +246 -0
  157. package/dist/content/examples/notes/tsconfig.json +16 -0
  158. package/dist/content/examples/notes/view.ocml +154 -0
  159. package/dist/content/examples/performance/LICENSE +21 -0
  160. package/dist/content/examples/performance/README.md +134 -0
  161. package/dist/content/examples/performance/listing.json +19 -0
  162. package/dist/content/examples/performance/locales/en.json +20 -0
  163. package/dist/content/examples/performance/locales/fr.json +20 -0
  164. package/dist/content/examples/performance/logic.ts +210 -0
  165. package/dist/content/examples/performance/manifest.json +83 -0
  166. package/dist/content/examples/performance/package.json +16 -0
  167. package/dist/content/examples/performance/style.ocss +75 -0
  168. package/dist/content/examples/performance/tests/fahrenheit.scenario.json +110 -0
  169. package/dist/content/examples/performance/tests/horizontal.scenario.json +96 -0
  170. package/dist/content/examples/performance/tests/load-logic.mjs +22 -0
  171. package/dist/content/examples/performance/tests/logic.test.mjs +226 -0
  172. package/dist/content/examples/performance/tests/menu.scenario.json +170 -0
  173. package/dist/content/examples/performance/tests/no-fps-source.scenario.json +47 -0
  174. package/dist/content/examples/performance/tests/preview.scenario.json +45 -0
  175. package/dist/content/examples/performance/tests/refused.scenario.json +20 -0
  176. package/dist/content/examples/performance/tests/refused.test.mjs +19 -0
  177. package/dist/content/examples/performance/tests/region.scenario.json +89 -0
  178. package/dist/content/examples/performance/tests/render.scenario.json +112 -0
  179. package/dist/content/examples/performance/tests/sources.scenario.json +151 -0
  180. package/dist/content/examples/performance/tests/stale.scenario.json +111 -0
  181. package/dist/content/examples/performance/tests/telemetry-only.scenario.json +44 -0
  182. package/dist/content/examples/performance/tests/thresholds.scenario.json +147 -0
  183. package/dist/content/examples/performance/tests/waiting.scenario.json +105 -0
  184. package/dist/content/examples/performance/tsconfig.json +16 -0
  185. package/dist/content/examples/performance/view.ocml +18 -0
  186. package/dist/content/examples/playervox-journal/LICENSE +21 -0
  187. package/dist/content/examples/playervox-journal/README.md +150 -0
  188. package/dist/content/examples/playervox-journal/listing.json +19 -0
  189. package/dist/content/examples/playervox-journal/locales/en.json +25 -0
  190. package/dist/content/examples/playervox-journal/locales/fr.json +25 -0
  191. package/dist/content/examples/playervox-journal/logic.ts +556 -0
  192. package/dist/content/examples/playervox-journal/manifest.json +19 -0
  193. package/dist/content/examples/playervox-journal/package.json +16 -0
  194. package/dist/content/examples/playervox-journal/style.ocss +142 -0
  195. package/dist/content/examples/playervox-journal/tests/changes.scenario.json +482 -0
  196. package/dist/content/examples/playervox-journal/tests/delete.scenario.json +432 -0
  197. package/dist/content/examples/playervox-journal/tests/frame.scenario.json +169 -0
  198. package/dist/content/examples/playervox-journal/tests/load-logic.mjs +22 -0
  199. package/dist/content/examples/playervox-journal/tests/logic.test.mjs +349 -0
  200. package/dist/content/examples/playervox-journal/tests/paging.scenario.json +428 -0
  201. package/dist/content/examples/playervox-journal/tests/preview.scenario.json +184 -0
  202. package/dist/content/examples/playervox-journal/tests/read-only.scenario.json +157 -0
  203. package/dist/content/examples/playervox-journal/tests/refused.scenario.json +39 -0
  204. package/dist/content/examples/playervox-journal/tests/scale.scenario.json +286 -0
  205. package/dist/content/examples/playervox-journal/tests/signed-out.scenario.json +134 -0
  206. package/dist/content/examples/playervox-journal/tests/states.scenario.json +741 -0
  207. package/dist/content/examples/playervox-journal/tests/unavailable.scenario.json +183 -0
  208. package/dist/content/examples/playervox-journal/tsconfig.json +16 -0
  209. package/dist/content/examples/playervox-journal/view.ocml +52 -0
  210. package/dist/content/examples/playervox-rating/LICENSE +21 -0
  211. package/dist/content/examples/playervox-rating/README.md +166 -0
  212. package/dist/content/examples/playervox-rating/listing.json +19 -0
  213. package/dist/content/examples/playervox-rating/locales/en.json +35 -0
  214. package/dist/content/examples/playervox-rating/locales/fr.json +35 -0
  215. package/dist/content/examples/playervox-rating/logic.ts +581 -0
  216. package/dist/content/examples/playervox-rating/manifest.json +19 -0
  217. package/dist/content/examples/playervox-rating/package.json +16 -0
  218. package/dist/content/examples/playervox-rating/style.ocss +333 -0
  219. package/dist/content/examples/playervox-rating/tests/account.scenario.json +125 -0
  220. package/dist/content/examples/playervox-rating/tests/bounds.scenario.json +73 -0
  221. package/dist/content/examples/playervox-rating/tests/draft.scenario.json +199 -0
  222. package/dist/content/examples/playervox-rating/tests/form.scenario.json +142 -0
  223. package/dist/content/examples/playervox-rating/tests/load-logic.mjs +22 -0
  224. package/dist/content/examples/playervox-rating/tests/logic.test.mjs +346 -0
  225. package/dist/content/examples/playervox-rating/tests/offline.scenario.json +142 -0
  226. package/dist/content/examples/playervox-rating/tests/preview.scenario.json +44 -0
  227. package/dist/content/examples/playervox-rating/tests/publish-keyboard.scenario.json +221 -0
  228. package/dist/content/examples/playervox-rating/tests/publish-pointer.scenario.json +314 -0
  229. package/dist/content/examples/playervox-rating/tests/read-only.scenario.json +70 -0
  230. package/dist/content/examples/playervox-rating/tests/refused.scenario.json +45 -0
  231. package/dist/content/examples/playervox-rating/tests/states.scenario.json +362 -0
  232. package/dist/content/examples/playervox-rating/tsconfig.json +16 -0
  233. package/dist/content/examples/playervox-rating/view.ocml +62 -0
  234. package/dist/content/examples/playervox-reviews/LICENSE +21 -0
  235. package/dist/content/examples/playervox-reviews/README.md +194 -0
  236. package/dist/content/examples/playervox-reviews/listing.json +19 -0
  237. package/dist/content/examples/playervox-reviews/locales/en.json +32 -0
  238. package/dist/content/examples/playervox-reviews/locales/fr.json +32 -0
  239. package/dist/content/examples/playervox-reviews/logic.ts +822 -0
  240. package/dist/content/examples/playervox-reviews/manifest.json +30 -0
  241. package/dist/content/examples/playervox-reviews/package.json +16 -0
  242. package/dist/content/examples/playervox-reviews/style.ocss +260 -0
  243. package/dist/content/examples/playervox-reviews/tests/account.scenario.json +249 -0
  244. package/dist/content/examples/playervox-reviews/tests/bounds.scenario.json +336 -0
  245. package/dist/content/examples/playervox-reviews/tests/changes.scenario.json +515 -0
  246. package/dist/content/examples/playervox-reviews/tests/filter.scenario.json +400 -0
  247. package/dist/content/examples/playervox-reviews/tests/frame.scenario.json +185 -0
  248. package/dist/content/examples/playervox-reviews/tests/keyboard.scenario.json +188 -0
  249. package/dist/content/examples/playervox-reviews/tests/load-logic.mjs +22 -0
  250. package/dist/content/examples/playervox-reviews/tests/logic.test.mjs +559 -0
  251. package/dist/content/examples/playervox-reviews/tests/offline-start.scenario.json +152 -0
  252. package/dist/content/examples/playervox-reviews/tests/offline.scenario.json +311 -0
  253. package/dist/content/examples/playervox-reviews/tests/paging.scenario.json +497 -0
  254. package/dist/content/examples/playervox-reviews/tests/preview.scenario.json +286 -0
  255. package/dist/content/examples/playervox-reviews/tests/reading.scenario.json +591 -0
  256. package/dist/content/examples/playervox-reviews/tests/refused.scenario.json +54 -0
  257. package/dist/content/examples/playervox-reviews/tests/scale.scenario.json +303 -0
  258. package/dist/content/examples/playervox-reviews/tests/states.scenario.json +700 -0
  259. package/dist/content/examples/playervox-reviews/tests/unavailable.scenario.json +192 -0
  260. package/dist/content/examples/playervox-reviews/tsconfig.json +16 -0
  261. package/dist/content/examples/playervox-reviews/view.ocml +110 -0
  262. package/dist/content/examples/playervox-score/LICENSE +21 -0
  263. package/dist/content/examples/playervox-score/README.md +131 -0
  264. package/dist/content/examples/playervox-score/listing.json +19 -0
  265. package/dist/content/examples/playervox-score/locales/en.json +23 -0
  266. package/dist/content/examples/playervox-score/locales/fr.json +23 -0
  267. package/dist/content/examples/playervox-score/logic.ts +430 -0
  268. package/dist/content/examples/playervox-score/manifest.json +47 -0
  269. package/dist/content/examples/playervox-score/package.json +16 -0
  270. package/dist/content/examples/playervox-score/style.ocss +247 -0
  271. package/dist/content/examples/playervox-score/tests/grades.scenario.json +279 -0
  272. package/dist/content/examples/playervox-score/tests/load-logic.mjs +22 -0
  273. package/dist/content/examples/playervox-score/tests/logic.test.mjs +229 -0
  274. package/dist/content/examples/playervox-score/tests/options.scenario.json +302 -0
  275. package/dist/content/examples/playervox-score/tests/preview.scenario.json +68 -0
  276. package/dist/content/examples/playervox-score/tests/pulse.scenario.json +157 -0
  277. package/dist/content/examples/playervox-score/tests/refused.scenario.json +21 -0
  278. package/dist/content/examples/playervox-score/tests/signed-out.scenario.json +46 -0
  279. package/dist/content/examples/playervox-score/tests/states.scenario.json +450 -0
  280. package/dist/content/examples/playervox-score/tests/unavailable.scenario.json +84 -0
  281. package/dist/content/examples/playervox-score/tsconfig.json +16 -0
  282. package/dist/content/examples/playervox-score/view.ocml +51 -0
  283. package/dist/content/examples/session/LICENSE +21 -0
  284. package/dist/content/examples/session/README.md +57 -0
  285. package/dist/content/examples/session/listing.json +19 -0
  286. package/dist/content/examples/session/locales/en.json +3 -0
  287. package/dist/content/examples/session/locales/fr.json +3 -0
  288. package/dist/content/examples/session/logic.ts +26 -0
  289. package/dist/content/examples/session/manifest.json +19 -0
  290. package/dist/content/examples/session/package.json +16 -0
  291. package/dist/content/examples/session/style.ocss +16 -0
  292. package/dist/content/examples/session/tests/hidden.scenario.json +45 -0
  293. package/dist/content/examples/session/tests/load-logic.mjs +22 -0
  294. package/dist/content/examples/session/tests/logic.test.mjs +37 -0
  295. package/dist/content/examples/session/tests/preview.scenario.json +28 -0
  296. package/dist/content/examples/session/tests/refused.scenario.json +20 -0
  297. package/dist/content/examples/session/tests/refused.test.mjs +16 -0
  298. package/dist/content/examples/session/tests/render.scenario.json +95 -0
  299. package/dist/content/examples/session/tests/ticks.scenario.json +139 -0
  300. package/dist/content/examples/session/tests/unknown.scenario.json +37 -0
  301. package/dist/content/examples/session/tsconfig.json +16 -0
  302. package/dist/content/examples/session/view.ocml +11 -0
  303. package/dist/content/examples/stopwatch/LICENSE +21 -0
  304. package/dist/content/examples/stopwatch/README.md +99 -0
  305. package/dist/content/examples/stopwatch/listing.json +19 -0
  306. package/dist/content/examples/stopwatch/locales/en.json +8 -0
  307. package/dist/content/examples/stopwatch/locales/fr.json +8 -0
  308. package/dist/content/examples/stopwatch/logic.ts +93 -0
  309. package/dist/content/examples/stopwatch/manifest.json +19 -0
  310. package/dist/content/examples/stopwatch/package.json +16 -0
  311. package/dist/content/examples/stopwatch/style.ocss +54 -0
  312. package/dist/content/examples/stopwatch/tests/errors.scenario.json +121 -0
  313. package/dist/content/examples/stopwatch/tests/gesture.scenario.json +154 -0
  314. package/dist/content/examples/stopwatch/tests/hidden.scenario.json +57 -0
  315. package/dist/content/examples/stopwatch/tests/hundredths.scenario.json +173 -0
  316. package/dist/content/examples/stopwatch/tests/load-logic.mjs +22 -0
  317. package/dist/content/examples/stopwatch/tests/logic.test.mjs +99 -0
  318. package/dist/content/examples/stopwatch/tests/preview.scenario.json +36 -0
  319. package/dist/content/examples/stopwatch/tests/read-only.scenario.json +42 -0
  320. package/dist/content/examples/stopwatch/tests/read-only.test.mjs +18 -0
  321. package/dist/content/examples/stopwatch/tests/refused.scenario.json +22 -0
  322. package/dist/content/examples/stopwatch/tests/refused.test.mjs +18 -0
  323. package/dist/content/examples/stopwatch/tests/render.scenario.json +105 -0
  324. package/dist/content/examples/stopwatch/tests/states.scenario.json +246 -0
  325. package/dist/content/examples/stopwatch/tests/unknown.scenario.json +40 -0
  326. package/dist/content/examples/stopwatch/tsconfig.json +16 -0
  327. package/dist/content/examples/stopwatch/view.ocml +29 -0
  328. package/dist/content/examples/twitch-chat/LICENSE +21 -0
  329. package/dist/content/examples/twitch-chat/README.md +283 -0
  330. package/dist/content/examples/twitch-chat/listing.json +19 -0
  331. package/dist/content/examples/twitch-chat/locales/en.json +52 -0
  332. package/dist/content/examples/twitch-chat/locales/fr.json +52 -0
  333. package/dist/content/examples/twitch-chat/logic.ts +887 -0
  334. package/dist/content/examples/twitch-chat/manifest.json +33 -0
  335. package/dist/content/examples/twitch-chat/package.json +16 -0
  336. package/dist/content/examples/twitch-chat/style.ocss +544 -0
  337. package/dist/content/examples/twitch-chat/tests/account.scenario.json +325 -0
  338. package/dist/content/examples/twitch-chat/tests/burst.scenario.json +1866 -0
  339. package/dist/content/examples/twitch-chat/tests/channel-refused.scenario.json +372 -0
  340. package/dist/content/examples/twitch-chat/tests/channel.scenario.json +480 -0
  341. package/dist/content/examples/twitch-chat/tests/draft.scenario.json +279 -0
  342. package/dist/content/examples/twitch-chat/tests/fade.scenario.json +269 -0
  343. package/dist/content/examples/twitch-chat/tests/failures.scenario.json +420 -0
  344. package/dist/content/examples/twitch-chat/tests/favorites.scenario.json +376 -0
  345. package/dist/content/examples/twitch-chat/tests/frame.scenario.json +152 -0
  346. package/dist/content/examples/twitch-chat/tests/lifetime.scenario.json +186 -0
  347. package/dist/content/examples/twitch-chat/tests/load-logic.mjs +22 -0
  348. package/dist/content/examples/twitch-chat/tests/logic.test.mjs +1053 -0
  349. package/dist/content/examples/twitch-chat/tests/passive.scenario.json +536 -0
  350. package/dist/content/examples/twitch-chat/tests/preview.scenario.json +147 -0
  351. package/dist/content/examples/twitch-chat/tests/read-only.scenario.json +202 -0
  352. package/dist/content/examples/twitch-chat/tests/read-only.test.mjs +63 -0
  353. package/dist/content/examples/twitch-chat/tests/refused.scenario.json +65 -0
  354. package/dist/content/examples/twitch-chat/tests/refused.test.mjs +43 -0
  355. package/dist/content/examples/twitch-chat/tests/render.scenario.json +347 -0
  356. package/dist/content/examples/twitch-chat/tests/reply.scenario.json +384 -0
  357. package/dist/content/examples/twitch-chat/tests/send-refused.scenario.json +383 -0
  358. package/dist/content/examples/twitch-chat/tests/send.scenario.json +300 -0
  359. package/dist/content/examples/twitch-chat/tests/states.scenario.json +455 -0
  360. package/dist/content/examples/twitch-chat/tests/unavailable.scenario.json +256 -0
  361. package/dist/content/examples/twitch-chat/tests/unread.scenario.json +997 -0
  362. package/dist/content/examples/twitch-chat/tsconfig.json +16 -0
  363. package/dist/content/examples/twitch-chat/view.ocml +141 -0
  364. package/dist/content/examples/warframe-market/LICENSE +21 -0
  365. package/dist/content/examples/warframe-market/README.md +176 -0
  366. package/dist/content/examples/warframe-market/listing.json +19 -0
  367. package/dist/content/examples/warframe-market/locales/en.json +44 -0
  368. package/dist/content/examples/warframe-market/locales/fr.json +44 -0
  369. package/dist/content/examples/warframe-market/logic.ts +1265 -0
  370. package/dist/content/examples/warframe-market/manifest.json +92 -0
  371. package/dist/content/examples/warframe-market/package.json +16 -0
  372. package/dist/content/examples/warframe-market/style.ocss +422 -0
  373. package/dist/content/examples/warframe-market/tests/bounds.scenario.json +60 -0
  374. package/dist/content/examples/warframe-market/tests/cached.scenario.json +70 -0
  375. package/dist/content/examples/warframe-market/tests/detail.scenario.json +251 -0
  376. package/dist/content/examples/warframe-market/tests/errors.scenario.json +105 -0
  377. package/dist/content/examples/warframe-market/tests/fixtures.json +295 -0
  378. package/dist/content/examples/warframe-market/tests/load-logic.mjs +22 -0
  379. package/dist/content/examples/warframe-market/tests/logic.test.mjs +505 -0
  380. package/dist/content/examples/warframe-market/tests/no-clipboard.scenario.json +104 -0
  381. package/dist/content/examples/warframe-market/tests/passive.scenario.json +87 -0
  382. package/dist/content/examples/warframe-market/tests/preview.scenario.json +146 -0
  383. package/dist/content/examples/warframe-market/tests/refresh.scenario.json +56 -0
  384. package/dist/content/examples/warframe-market/tests/refused.scenario.json +74 -0
  385. package/dist/content/examples/warframe-market/tests/render.scenario.json +210 -0
  386. package/dist/content/examples/warframe-market/tests/search.scenario.json +189 -0
  387. package/dist/content/examples/warframe-market/tests/unavailable.scenario.json +54 -0
  388. package/dist/content/examples/warframe-market/tsconfig.json +16 -0
  389. package/dist/content/examples/warframe-market/view.ocml +116 -0
  390. package/dist/content/examples/weather/LICENSE +20 -0
  391. package/dist/content/examples/weather/listing.json +10 -0
  392. package/dist/content/examples/weather/locales/en.json +9 -0
  393. package/dist/content/examples/weather/locales/fr.json +9 -0
  394. package/dist/content/examples/weather/logic.ts +62 -0
  395. package/dist/content/examples/weather/manifest.json +51 -0
  396. package/dist/content/examples/weather/package.json +15 -0
  397. package/dist/content/examples/weather/style.ocss +29 -0
  398. package/dist/content/examples/weather/tsconfig.json +16 -0
  399. package/dist/content/examples/weather/view.ocml +12 -0
  400. package/dist/content/index.json +3601 -0
  401. package/dist/content/reference/errors.json +1712 -0
  402. package/dist/content/reference/limits.json +1154 -0
  403. package/dist/content/reference/manifest.md +223 -0
  404. package/dist/content/reference/permissions.json +413 -0
  405. package/dist/content/templates/blank/locales/en.json +3 -0
  406. package/dist/content/templates/blank/locales/fr.json +3 -0
  407. package/dist/content/templates/blank/logic.ts +7 -0
  408. package/dist/content/templates/blank/manifest.json +16 -0
  409. package/dist/content/templates/blank/style.ocss +10 -0
  410. package/dist/content/templates/blank/tests/example.scenario.json +10 -0
  411. package/dist/content/templates/blank/view.ocml +6 -0
  412. package/dist/content/templates/chart/locales/en.json +3 -0
  413. package/dist/content/templates/chart/locales/fr.json +3 -0
  414. package/dist/content/templates/chart/logic.ts +27 -0
  415. package/dist/content/templates/chart/manifest.json +16 -0
  416. package/dist/content/templates/chart/style.ocss +27 -0
  417. package/dist/content/templates/chart/tests/example.scenario.json +10 -0
  418. package/dist/content/templates/chart/view.ocml +10 -0
  419. package/dist/content/templates/counter/locales/en.json +4 -0
  420. package/dist/content/templates/counter/locales/fr.json +4 -0
  421. package/dist/content/templates/counter/logic.ts +20 -0
  422. package/dist/content/templates/counter/manifest.json +16 -0
  423. package/dist/content/templates/counter/style.ocss +26 -0
  424. package/dist/content/templates/counter/tests/example.scenario.json +11 -0
  425. package/dist/content/templates/counter/view.ocml +13 -0
  426. package/dist/content/templates/list/locales/en.json +8 -0
  427. package/dist/content/templates/list/locales/fr.json +8 -0
  428. package/dist/content/templates/list/logic.ts +35 -0
  429. package/dist/content/templates/list/manifest.json +16 -0
  430. package/dist/content/templates/list/style.ocss +42 -0
  431. package/dist/content/templates/list/tests/example.scenario.json +10 -0
  432. package/dist/content/templates/list/view.ocml +17 -0
  433. package/dist/content/templates/shared/LICENSE +20 -0
  434. package/dist/content/templates/shared/package.json +14 -0
  435. package/dist/content/templates/shared/tsconfig.json +16 -0
  436. package/dist/content.js +68 -0
  437. package/dist/env.js +156 -0
  438. package/dist/fingerprint.js +32 -0
  439. package/dist/ids.js +27 -0
  440. package/dist/index.js +82 -0
  441. package/dist/knowledge.js +222 -0
  442. package/dist/npm.js +149 -0
  443. package/dist/prompts.js +47 -0
  444. package/dist/resources.js +82 -0
  445. package/dist/run.js +120 -0
  446. package/dist/server.js +43 -0
  447. package/dist/session.js +205 -0
  448. package/dist/sources.js +218 -0
  449. package/dist/text.js +80 -0
  450. package/dist/texts.js +44 -0
  451. package/dist/tools/audit.js +105 -0
  452. package/dist/tools/build.js +453 -0
  453. package/dist/tools/common.js +122 -0
  454. package/dist/tools/docs.js +196 -0
  455. package/dist/tools/preview.js +87 -0
  456. package/dist/tools/project.js +178 -0
  457. package/dist/tools/setup.js +146 -0
  458. package/dist/tools/submission.js +369 -0
  459. package/dist/version.js +3 -0
  460. package/npm-shrinkwrap.json +402 -0
  461. package/package.json +52 -4
@@ -0,0 +1,581 @@
1
+ // PlayerVox Rating: the user's own PlayerVox rating of the active Steam
2
+ // game. OverCrow reads the rating and follows PlayerVox's live updates; in
3
+ // Interactive mode the widget shows a form whose three sliders and review
4
+ // belong to OverCrow: it fills them, keeps the draft, and sends their
5
+ // values itself when the user publishes. The logic only follows them.
6
+ import {
7
+ draw,
8
+ hasGrant,
9
+ host,
10
+ initState,
11
+ onHost,
12
+ playervox,
13
+ t,
14
+ timers,
15
+ type Color,
16
+ type DrawCommand,
17
+ type RatingState,
18
+ type ServiceErrorCode,
19
+ type ServiceErrorPayload,
20
+ type Subscription,
21
+ type Timer,
22
+ } from "@overcrow/sdk";
23
+
24
+ /** The three criteria of a rating, in the form's order. */
25
+ export type CriterionKey = "gameplay" | "art" | "tech";
26
+
27
+ declare module "@overcrow/sdk" {
28
+ interface WidgetState {
29
+ /** The latest rating state; `null` until the host's first answer. */
30
+ rating: RatingState | null;
31
+ /** The form's sliders as the host last reported them. */
32
+ gameplay: number | null;
33
+ art: number | null;
34
+ tech: number | null;
35
+ /** The form's review as the host last reported it. */
36
+ review: string;
37
+ /** Publications sent and not answered yet. */
38
+ pending: number;
39
+ /** The last publication was stored and nothing changed since. */
40
+ saved: boolean;
41
+ /** Message key of the last publication's failure, or `""`. */
42
+ error: string;
43
+ /** The form takes input only in Interactive mode. */
44
+ interactive: boolean;
45
+ /** The badge canvas's classes, with its pulse. */
46
+ badgeClass: string;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * After the host ends the subscription with a failure, the widget
52
+ * subscribes again this much later.
53
+ */
54
+ export const RETRY_MS = 5_000;
55
+
56
+ /**
57
+ * OverCrow answers a publication within 30 s (`timeout` beyond). Should no
58
+ * answer come at all, the button is given back after this long.
59
+ */
60
+ export const ANSWER_MS = 40_000;
61
+
62
+ /** `label` bound (`MAX_LABEL_BYTES`). */
63
+ const MAX_LABEL_BYTES = 256;
64
+
65
+ // <shared:playervox-badge/badge.ts>
66
+ // The PlayerVox grade badge and mark, shared by the Score and Rating
67
+ // built-ins; the Journal draws the mark alone, and Player reviews the mark
68
+ // and a smaller badge per review. scripts/sync-shared-widgets.mjs
69
+ // copies this file, as is, into
70
+ // their logic.ts between the markers `// <shared:playervox-badge/badge.ts>`;
71
+ // edit it here, never in a copy. The copy relies on the widget importing
72
+ // the `Color` and `DrawCommand` types from @overcrow/sdk.
73
+
74
+ /** A PlayerVox grade, or `--` without a valid score. */
75
+ export type BadgeGrade = "S+" | "S" | "A" | "B" | "C" | "D" | "F" | "--";
76
+
77
+ /**
78
+ * The grade of a 0–100 value with PlayerVox's thresholds, on the raw value
79
+ * (89.6 is S, although it shows as 90); `--` for none or out of range. The
80
+ * host grades the overall score itself; widgets grade the criteria.
81
+ */
82
+ export function gradeOf(value: number | null): BadgeGrade {
83
+ if (value === null || !Number.isFinite(value) || value < 0 || value > 100) {
84
+ return "--";
85
+ }
86
+ if (value >= 90) return "S+";
87
+ if (value >= 80) return "S";
88
+ if (value >= 70) return "A";
89
+ if (value >= 60) return "B";
90
+ if (value >= 40) return "C";
91
+ if (value >= 20) return "D";
92
+ return "F";
93
+ }
94
+
95
+ /**
96
+ * The grade colours of PlayerVox's site (its Tailwind palette in sRGB), in
97
+ * both themes: the badge keeps its black face on a light panel too.
98
+ */
99
+ export const GRADE_COLORS: Readonly<Record<BadgeGrade, Color>> = {
100
+ "S+": "#9ae600",
101
+ S: "#7ccf00",
102
+ A: "#00d3f3",
103
+ B: "#fdc700",
104
+ C: "#fe9a00",
105
+ D: "#e7000b",
106
+ F: "#52525c",
107
+ "--": "#52525c",
108
+ };
109
+
110
+ /** The style class of a grade's colour (`badge.ocss`): `grade-s-plus`… */
111
+ export function gradeClass(grade: BadgeGrade): string {
112
+ return grade === "--" ? "grade-none" : `grade-${grade.toLowerCase().replace("+", "-plus")}`;
113
+ }
114
+
115
+ /**
116
+ * Side of the badge canvas in logical px: the 56 px badge turned by 4°,
117
+ * its 3 px border and the S+ glow, centred.
118
+ */
119
+ export const BADGE_CANVAS = 76;
120
+
121
+ const BADGE_HALF = 28;
122
+ const BADGE_RADIUS = 12;
123
+ /** The letter's baseline below the centre: the capitals sit centred. */
124
+ const LETTER_BASELINE = 9;
125
+
126
+ /**
127
+ * The neutral `--` badge per theme: a translucent face and a grey border
128
+ * and text that keep their contrast on the panel. A graded badge keeps its
129
+ * black face in both themes.
130
+ */
131
+ const NEUTRAL: Readonly<Record<"dark" | "light", { face: Color; color: Color }>> = {
132
+ dark: { face: "#27272a80", color: "#52525c" },
133
+ light: { face: "#e4e4e780", color: "#71717a" },
134
+ };
135
+
136
+ /**
137
+ * The badge of a grade: a rounded square with a black face and a border
138
+ * of the grade's colour, turned by 4° with its letter (not `--`, which is
139
+ * neutral and square), and a faint static glow for S+.
140
+ */
141
+ export function badgeCommands(grade: BadgeGrade, theme: "dark" | "light"): DrawCommand[] {
142
+ const neutral = grade === "--" ? NEUTRAL[theme] : null;
143
+ const color = neutral?.color ?? GRADE_COLORS[grade];
144
+ const centre = BADGE_CANVAS / 2;
145
+ const square = (): DrawCommand => [
146
+ "rect",
147
+ -BADGE_HALF,
148
+ -BADGE_HALF,
149
+ BADGE_HALF * 2,
150
+ BADGE_HALF * 2,
151
+ BADGE_RADIUS,
152
+ ];
153
+ const commands: DrawCommand[] = [
154
+ ["save"],
155
+ ["translate", centre, centre],
156
+ ["rotate", grade === "--" ? 0 : 4],
157
+ ];
158
+ if (grade === "S+") {
159
+ // Eight widening rings of the accent at 2 % each: a soft halo.
160
+ for (let ring = 8; ring >= 1; ring -= 1) {
161
+ commands.push(square(), ["stroke", "#a3e63505", 3 + ring * 1.5]);
162
+ }
163
+ }
164
+ commands.push(
165
+ square(),
166
+ ["fill", neutral?.face ?? "#000000"],
167
+ square(),
168
+ ["stroke", color, 3],
169
+ ["text", 0, LETTER_BASELINE, grade, 26, color, "center", "display", "700"],
170
+ ["restore"],
171
+ );
172
+ return commands;
173
+ }
174
+
175
+ /**
176
+ * The badge's pulse class after `current` (`badge.ocss`): it alternates, so
177
+ * that each new score restarts the host's animation.
178
+ */
179
+ export function nextPulse(current: string): "pulse-a" | "pulse-b" {
180
+ return current === "pulse-a" ? "pulse-b" : "pulse-a";
181
+ }
182
+
183
+ /** The accessible name of a badge: "Grade S+, 94/100". */
184
+ export function badgeLabel(grade: BadgeGrade, score: number | null, words: { grade: string; none: string }): string {
185
+ if (grade === "--" || score === null) {
186
+ return words.none;
187
+ }
188
+ return `${words.grade} ${grade}, ${roundHalfAway(score)}/100`;
189
+ }
190
+
191
+ /** Rounds half away from zero: 72.5 is 73 (parity sheet decision 6). */
192
+ export function roundHalfAway(value: number): number {
193
+ return Math.sign(value) * Math.round(Math.abs(value));
194
+ }
195
+
196
+ // The PlayerVox mark: three rising bars, in unit coordinates of its box.
197
+ const MARK_BARS: readonly (readonly number[])[] = [
198
+ [0.09403, 0.29375, 0.04806, 0.3068, 0.02098, 0.34, 0.01785, 0.38207, 0.01785, 0.42385, 0.01785, 0.46563, 0.01785, 0.50742, 0.01785, 0.5492, 0.01785, 0.59098, 0.01785, 0.63276, 0.01785, 0.67454, 0.01785, 0.71632, 0.01785, 0.75811, 0.01785, 0.79989, 0.01785, 0.84167, 0.01785, 0.88345, 0.01785, 0.92523, 0.01785, 0.96701, 0.04115, 0.96149, 0.07617, 0.93218, 0.11119, 0.90286, 0.14621, 0.87354, 0.18123, 0.84422, 0.21625, 0.8149, 0.25127, 0.78558, 0.28629, 0.75626, 0.31138, 0.72341, 0.31138, 0.68162, 0.31138, 0.63984, 0.31138, 0.59806, 0.31138, 0.55628, 0.31138, 0.5145, 0.31138, 0.47272, 0.31138, 0.43094, 0.31138, 0.38915, 0.30981, 0.34705, 0.28603, 0.31194, 0.24179, 0.29574, 0.19233, 0.29497, 0.14318, 0.29436],
199
+ [0.43235, 0.1525, 0.38372, 0.16747, 0.35807, 0.20475, 0.35676, 0.24975, 0.35676, 0.29442, 0.35676, 0.33908, 0.35676, 0.38375, 0.35676, 0.42842, 0.35676, 0.47308, 0.35676, 0.51775, 0.35676, 0.56242, 0.35676, 0.60708, 0.35676, 0.65175, 0.35676, 0.69642, 0.3648, 0.73425, 0.41735, 0.73425, 0.4699, 0.73425, 0.52245, 0.73425, 0.575, 0.73425, 0.62755, 0.73425, 0.65059, 0.70917, 0.65059, 0.6645, 0.65059, 0.61983, 0.65059, 0.57517, 0.65059, 0.5305, 0.65059, 0.48583, 0.65059, 0.44117, 0.65059, 0.3965, 0.65059, 0.35183, 0.65059, 0.30717, 0.65059, 0.2625, 0.65059, 0.21783, 0.63377, 0.17623, 0.59039, 0.15382, 0.53745, 0.1525, 0.4849, 0.1525],
200
+ [0.76765, 0.01375, 0.71253, 0.03393, 0.69206, 0.08189, 0.69206, 0.13433, 0.69206, 0.18678, 0.69206, 0.23922, 0.69206, 0.29167, 0.69206, 0.34411, 0.69206, 0.39656, 0.69206, 0.449, 0.69206, 0.50144, 0.69206, 0.55389, 0.69206, 0.60633, 0.69206, 0.65878, 0.69206, 0.71122, 0.7252, 0.7355, 0.7869, 0.7355, 0.84859, 0.7355, 0.91029, 0.7355, 0.97199, 0.7355, 0.98588, 0.69486, 0.98588, 0.64242, 0.98588, 0.58997, 0.98588, 0.53753, 0.98588, 0.48508, 0.98588, 0.43264, 0.98588, 0.38019, 0.98588, 0.32775, 0.98588, 0.27531, 0.98588, 0.22286, 0.98588, 0.17042, 0.98588, 0.11797, 0.98438, 0.06513, 0.95084, 0.02369, 0.89105, 0.01375, 0.82935, 0.01375],
201
+ ];
202
+
203
+ /**
204
+ * The PlayerVox mark, `height` px tall and 0.85 as wide, filled with
205
+ * `color` and centred in a canvas `canvasWidth` px wide: give the canvas a
206
+ * whole number of pixels, so that its texture is never resampled.
207
+ */
208
+ export function markCommands(canvasWidth: number, height: number, color: Color): DrawCommand[] {
209
+ const width = height * 0.85;
210
+ const left = (canvasWidth - width) / 2;
211
+ const commands: DrawCommand[] = [];
212
+ for (const bar of MARK_BARS) {
213
+ for (let index = 0; index + 1 < bar.length; index += 2) {
214
+ const x = left + (bar[index] ?? 0) * width;
215
+ const y = (bar[index + 1] ?? 0) * height;
216
+ commands.push(index === 0 ? ["moveTo", x, y] : ["lineTo", x, y]);
217
+ }
218
+ commands.push(["close"], ["fill", color]);
219
+ }
220
+ return commands;
221
+ }
222
+ // </shared:playervox-badge/badge.ts>
223
+
224
+ const canRead = hasGrant("playervox.rating.read");
225
+ const canWrite = hasGrant("playervox.rating.write");
226
+
227
+ const state = initState({
228
+ rating: null as RatingState | null,
229
+ gameplay: null as number | null,
230
+ art: null as number | null,
231
+ tech: null as number | null,
232
+ review: "",
233
+ pending: 0,
234
+ saved: false,
235
+ error: "",
236
+ interactive: host.mode === "interactive",
237
+ badgeClass: "badge",
238
+ });
239
+
240
+ /** What the view reads of the state. */
241
+ export interface View {
242
+ rating: RatingState | null;
243
+ gameplay: number | null;
244
+ art: number | null;
245
+ tech: number | null;
246
+ review: string;
247
+ pending: number;
248
+ saved: boolean;
249
+ error: string;
250
+ interactive: boolean;
251
+ }
252
+
253
+ /** The rating is known: the game's name, the grade and the form show. */
254
+ export function ready(rating: RatingState | null): boolean {
255
+ return rating !== null && rating.state === "ready";
256
+ }
257
+
258
+ /** The published rating of a ready state, or `null` before the first. */
259
+ function published(rating: RatingState | null): RatingState["rating"] {
260
+ return rating !== null && rating.state === "ready" ? rating.rating : null;
261
+ }
262
+
263
+ /** The mean of three criteria, or `null` while one is unknown. */
264
+ export function mean(gameplay: number | null, art: number | null, tech: number | null): number | null {
265
+ if (gameplay === null || art === null || tech === null) {
266
+ return null;
267
+ }
268
+ return (gameplay + art + tech) / 3;
269
+ }
270
+
271
+ /**
272
+ * The form shows: Interactive mode, the write grant and a known rating.
273
+ * In Passive mode it stays in the view, hidden, so that OverCrow keeps the
274
+ * draft its controls hold.
275
+ */
276
+ export function editing(view: View): boolean {
277
+ return view.interactive && canWrite && ready(view.rating);
278
+ }
279
+
280
+ /** The controls take input: the form shows and PlayerVox is reachable. */
281
+ export function editable(view: View): boolean {
282
+ return editing(view) && view.rating?.offline !== true;
283
+ }
284
+
285
+ /** The form differs from the published rating. */
286
+ export function dirty(view: View): boolean {
287
+ const rating = published(view.rating);
288
+ if (rating === null) {
289
+ return false;
290
+ }
291
+ return (
292
+ view.gameplay !== rating.gameplay ||
293
+ view.art !== rating.art ||
294
+ view.tech !== rating.tech ||
295
+ view.review !== (rating.review ?? "")
296
+ );
297
+ }
298
+
299
+ /**
300
+ * The score beside the badge: the draft's mean while the form shows, the
301
+ * published rating's otherwise; `null` without one.
302
+ */
303
+ export function shownScore(view: View): number | null {
304
+ if (editing(view)) {
305
+ const draft = mean(view.gameplay, view.art, view.tech);
306
+ if (draft !== null) {
307
+ return draft;
308
+ }
309
+ }
310
+ const rating = published(view.rating);
311
+ return rating === null ? null : mean(rating.gameplay, rating.art, rating.tech);
312
+ }
313
+
314
+ /** The rounded score, "--" without one: 92.67 shows "93". */
315
+ export function scoreText(view: View): string {
316
+ const score = shownScore(view);
317
+ return score === null ? "--" : String(roundHalfAway(score));
318
+ }
319
+
320
+ /** Message key under the score. */
321
+ export function caption(view: View): string {
322
+ const rating = published(view.rating);
323
+ if (editing(view) && (rating === null || dirty(view))) {
324
+ return "draft";
325
+ }
326
+ return rating === null ? "unrated" : "published";
327
+ }
328
+
329
+ /** Message keys of a state without a rating: a label and a hint. */
330
+ export interface Status {
331
+ label: string;
332
+ hint: string;
333
+ }
334
+
335
+ export function status(rating: RatingState | null): Status {
336
+ if (!canRead) {
337
+ return { label: "unavailable", hint: "no-permission" };
338
+ }
339
+ switch (rating === null ? "loading" : rating.state) {
340
+ case "idle":
341
+ return { label: "waiting", hint: "" };
342
+ case "unsupported":
343
+ return { label: "unsupported", hint: "steam-required" };
344
+ case "unavailable":
345
+ return { label: "unavailable", hint: "retrying" };
346
+ default:
347
+ return { label: "loading", hint: "" };
348
+ }
349
+ }
350
+
351
+ /** Provider text on one line. */
352
+ export function oneLine(text: string): string {
353
+ return text.replace(/\s*[\r\n]+\s*/g, " ").trim();
354
+ }
355
+
356
+ /** The game's name on PlayerVox, or `""`. */
357
+ export function gameName(rating: RatingState | null): string {
358
+ return rating !== null && rating.name !== null ? oneLine(rating.name) : "";
359
+ }
360
+
361
+ /** `text` cut to `limit` UTF-8 bytes on a character boundary. */
362
+ export function cutBytes(text: string, limit: number): string {
363
+ let bytes = 0;
364
+ let end = 0;
365
+ for (const character of text) {
366
+ const code = character.codePointAt(0) ?? 0;
367
+ const size = code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
368
+ if (bytes + size > limit) {
369
+ return text.slice(0, end);
370
+ }
371
+ bytes += size;
372
+ end += character.length;
373
+ }
374
+ return text;
375
+ }
376
+
377
+ /** The badge's accessible name: "Grade S+, 93/100", or "No grade". */
378
+ export function badgeName(view: View): string {
379
+ const score = shownScore(view);
380
+ return cutBytes(
381
+ badgeLabel(gradeOf(score), score, { grade: t("grade"), none: t("no-grade") }),
382
+ MAX_LABEL_BYTES,
383
+ );
384
+ }
385
+
386
+ /** A slider's value as text, "--" before OverCrow filled it. */
387
+ export function valueText(value: number | null): string {
388
+ return value === null ? "--" : String(value);
389
+ }
390
+
391
+ export function rootClass(view: View): string {
392
+ return view.rating?.offline === true ? "rating offline" : "rating";
393
+ }
394
+
395
+ export function formClass(view: View): string {
396
+ return editing(view) ? "form" : "form hidden";
397
+ }
398
+
399
+ /**
400
+ * The button is active when PlayerVox is reachable, nothing is being sent,
401
+ * and there is something to publish: a first rating, or a change.
402
+ */
403
+ export function canPublish(view: View): boolean {
404
+ return editable(view) && view.pending === 0 && (published(view.rating) === null || dirty(view));
405
+ }
406
+
407
+ /** Message key of the button. */
408
+ export function buttonLabel(view: View): string {
409
+ if (view.pending > 0) {
410
+ return "saving";
411
+ }
412
+ if (view.saved && !dirty(view)) {
413
+ return "saved";
414
+ }
415
+ return published(view.rating) === null ? "publish" : "update";
416
+ }
417
+
418
+ /** Message key of a refused publication. */
419
+ export function errorKey(code: ServiceErrorCode | null): string {
420
+ switch (code) {
421
+ case "invalid_request":
422
+ return "error-invalid";
423
+ case "not_connected":
424
+ return "error-expired";
425
+ case "busy":
426
+ return "error-busy";
427
+ case "stale_context":
428
+ return "error-stale";
429
+ case "permission_denied":
430
+ return "error-forbidden";
431
+ default:
432
+ // `unavailable`, `timeout`, `gesture_required` and the rest.
433
+ return "error-unavailable";
434
+ }
435
+ }
436
+
437
+ /** What the badge shows: redrawn when its grade or the theme changes. */
438
+ let drawn = "";
439
+ /** The published score shown: a pulse starts when it changes. */
440
+ let pulsed = "";
441
+
442
+ /** Draws the badge of the shown score and pulses it for a new published one. */
443
+ function showBadge(): void {
444
+ const grade = gradeOf(shownScore(state));
445
+ const key = `${grade}:${host.theme}`;
446
+ if (key !== drawn) {
447
+ draw("badge", badgeCommands(grade, host.theme));
448
+ drawn = key;
449
+ }
450
+ const rating = published(state.rating);
451
+ const score = rating === null ? null : mean(rating.gameplay, rating.art, rating.tech);
452
+ const shown = score === null ? "" : `${gradeOf(score)}:${roundHalfAway(score)}`;
453
+ if (shown !== "" && shown !== pulsed) {
454
+ const current = state.badgeClass.split(" ")[1] ?? "";
455
+ state.badgeClass = `badge ${nextPulse(current)}`;
456
+ }
457
+ pulsed = shown;
458
+ }
459
+
460
+ /** A value of the form changed: it is no longer the saved one. */
461
+ function edited(): void {
462
+ if (dirty(state)) {
463
+ state.saved = false;
464
+ }
465
+ showBadge();
466
+ }
467
+
468
+ /**
469
+ * A slider's value, from the user's drag or from OverCrow filling the
470
+ * form (the published rating, 50 before the first one).
471
+ */
472
+ export function scored(key: CriterionKey, value: number): void {
473
+ state[key] = value;
474
+ edited();
475
+ }
476
+
477
+ /** The review's text, from the user's typing or from OverCrow's fill. */
478
+ export function reviewed(text: string): void {
479
+ state.review = text;
480
+ edited();
481
+ }
482
+
483
+ let answer: Timer | null = null;
484
+
485
+ /** OverCrow is sending the form: the button waits for the outcome. */
486
+ function sending(): void {
487
+ state.pending += 1;
488
+ state.error = "";
489
+ state.saved = false;
490
+ answer?.cancel();
491
+ answer = timers.after(ANSWER_MS, () => {
492
+ answer = null;
493
+ if (state.pending > 0) {
494
+ state.pending = 0;
495
+ state.error = "error-unavailable";
496
+ }
497
+ });
498
+ }
499
+
500
+ /** The publish button: OverCrow submits the form on the same gesture. */
501
+ export function publishing(): void {
502
+ sending();
503
+ }
504
+
505
+ /** Ctrl+Enter in the review submits the form too. */
506
+ export function keyed(key: string, ctrl: boolean): void {
507
+ if (key === "Enter" && ctrl) {
508
+ sending();
509
+ }
510
+ }
511
+
512
+ /**
513
+ * The outcome of a publication OverCrow sent: the form's `submit` event,
514
+ * with the failure's code when it was rejected.
515
+ */
516
+ export function submitted(outcome: string, error: ServiceErrorPayload | undefined): void {
517
+ state.pending = Math.max(0, state.pending - 1);
518
+ if (state.pending === 0) {
519
+ answer?.cancel();
520
+ answer = null;
521
+ }
522
+ if (outcome === "accepted") {
523
+ // The subscription sends the stored rating; nothing is read again.
524
+ state.saved = true;
525
+ state.error = "";
526
+ } else if (outcome !== "cancelled") {
527
+ state.error = errorKey(error?.code ?? null);
528
+ }
529
+ }
530
+
531
+ /** The state shown once the host's source failed. */
532
+ const UNAVAILABLE: RatingState = { state: "unavailable", name: null, offline: false, rating: null };
533
+
534
+ let subscription: Subscription | null = null;
535
+
536
+ function show(rating: RatingState): void {
537
+ const before = state.rating;
538
+ if (before !== null && (before.state !== rating.state || before.name !== rating.name)) {
539
+ // Another game, or the rating left: the last outcome is not its.
540
+ state.saved = false;
541
+ state.error = "";
542
+ }
543
+ state.rating = rating;
544
+ showBadge();
545
+ }
546
+
547
+ function subscribe(): void {
548
+ const current = playervox.rating.subscribe((update) => {
549
+ if (update.ok) {
550
+ show(update.value);
551
+ return;
552
+ }
553
+ // The subscription ended: the host's source failed. Show it and ask
554
+ // again later; hidden, the timer waits until the widget shows.
555
+ if (subscription === current) {
556
+ subscription = null;
557
+ show(UNAVAILABLE);
558
+ timers.after(RETRY_MS, subscribe);
559
+ }
560
+ });
561
+ subscription = current;
562
+ }
563
+
564
+ onHost((changed) => {
565
+ if (changed.includes("mode")) {
566
+ state.interactive = host.mode === "interactive";
567
+ }
568
+ if (changed.includes("mode") || changed.includes("theme")) {
569
+ // The draft's score shows only with the form; the neutral badge
570
+ // follows the theme, the mark's token does already.
571
+ showBadge();
572
+ }
573
+ });
574
+
575
+ draw("mark", markCommands(16, 18, "var(--color-text-muted)"));
576
+ showBadge();
577
+ // Without the grant the host would refuse the subscription: say so instead
578
+ // of asking.
579
+ if (canRead) {
580
+ subscribe();
581
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "apiVersion": 1,
4
+ "id": "com.playervox.overcrow.playervox.rating",
5
+ "version": "1.0.2",
6
+ "name": {
7
+ "en": "My rating",
8
+ "fr": "Ma note"
9
+ },
10
+ "sizing": {
11
+ "fit": "height",
12
+ "preferred": { "width": 350, "height": 320 },
13
+ "min": { "width": 280, "height": 96 },
14
+ "max": { "width": 900, "height": 600 }
15
+ },
16
+ "permissions": {
17
+ "capabilities": ["playervox.rating.read", "playervox.rating.write"]
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "overcrow-widget-playervox-rating",
3
+ "version": "1.0.2",
4
+ "private": true,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "scripts": {
8
+ "check": "overcrow-widget check",
9
+ "package": "overcrow-widget package",
10
+ "test": "node --test tests/*.test.mjs"
11
+ },
12
+ "devDependencies": {
13
+ "@overcrow/sdk": "1.0.0",
14
+ "typescript": "6.0.3"
15
+ }
16
+ }