fedipod-server 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/dist/claims.js +1 -1
  2. package/dist/handler.d.ts +1 -1
  3. package/dist/handler.js +35 -2
  4. package/dist/handler.jsonld +1 -1
  5. package/lib/admin.mjs +3 -3
  6. package/lib/c2s.mjs +27 -2
  7. package/lib/deliver.mjs +6 -0
  8. package/lib/embed.mjs +1 -0
  9. package/lib/export-collections.mjs +1 -1
  10. package/lib/front-core.mjs +272 -49
  11. package/lib/gateway-core.mjs +3 -7
  12. package/lib/httpsig.mjs +33 -2
  13. package/lib/import.mjs +1 -1
  14. package/lib/intake.mjs +357 -118
  15. package/lib/links.mjs +6 -32
  16. package/lib/mastoapi.mjs +449 -38
  17. package/lib/migrate.mjs +4 -18
  18. package/lib/pod/README.md +61 -0
  19. package/lib/pod/actor.mjs +96 -0
  20. package/lib/pod/collection.mjs +77 -0
  21. package/lib/pod/containers.mjs +136 -0
  22. package/lib/pod/discovery.mjs +46 -0
  23. package/lib/pod/featured.mjs +18 -0
  24. package/lib/pod/followers.mjs +24 -0
  25. package/lib/pod/following.mjs +10 -0
  26. package/lib/pod/http.mjs +74 -0
  27. package/lib/pod/inbox.mjs +144 -0
  28. package/lib/pod/links.mjs +35 -0
  29. package/lib/pod/media.mjs +27 -0
  30. package/lib/pod/notes.mjs +80 -0
  31. package/lib/pod/notifications.mjs +89 -0
  32. package/lib/pod/outbox.mjs +18 -0
  33. package/lib/pod/package.json +19 -0
  34. package/lib/pod/policy.mjs +58 -0
  35. package/lib/pod/private.mjs +19 -0
  36. package/lib/pod/root.mjs +63 -0
  37. package/lib/pod/state.mjs +39 -0
  38. package/lib/pod/transport.mjs +477 -0
  39. package/lib/pod/urls.mjs +71 -0
  40. package/lib/polls.mjs +105 -0
  41. package/lib/publisher.mjs +357 -188
  42. package/lib/remote.mjs +30 -395
  43. package/lib/social.mjs +20 -9
  44. package/lib/storage.mjs +2 -2
  45. package/lib/store.mjs +8 -1
  46. package/lib/tagfeed.mjs +19 -2
  47. package/lib/wire.mjs +87 -67
  48. package/package.json +1 -1
  49. package/run-agent.mjs +20 -39
  50. package/web/admin/admin.js +141 -74
  51. package/web/admin/bar.css +9 -13
  52. package/web/admin/bar.js +2 -2
  53. package/web/admin/client/client.js +1 -1
  54. package/web/admin/client/index.html +5 -6
  55. package/web/admin/index.html +53 -65
  56. package/web/admin/setup/index.html +2 -1
  57. package/web/admin/setup/setup.js +1 -1
  58. package/web/admin/tokens.css +56 -0
  59. package/web/app/README.md +77 -0
  60. package/web/app/admin-facade.mjs +549 -0
  61. package/web/app/agent.mjs +490 -0
  62. package/web/app/atproto-browser.mjs +86 -0
  63. package/web/app/boot.mjs +340 -0
  64. package/web/app/deliver-relay.mjs +72 -0
  65. package/web/app/dist/boot.js +34067 -0
  66. package/web/app/dist/boot.js.map +7 -0
  67. package/web/app/dist/sw.js +53745 -0
  68. package/web/app/dist/sw.js.map +7 -0
  69. package/web/app/fediacct-browser.mjs +146 -0
  70. package/web/app/idb-kv.mjs +35 -0
  71. package/web/app/index.html +203 -0
  72. package/web/app/index.html~ +152 -0
  73. package/web/app/keys-browser.mjs +46 -0
  74. package/web/app/keystore.mjs +99 -0
  75. package/web/app/oidc-session.mjs +189 -0
  76. package/web/app/pod-auth.mjs +248 -0
  77. package/web/app/pod-remote.mjs +84 -0
  78. package/web/app/shims/_globals.mjs +2 -0
  79. package/web/app/shims/fedify-sig.mjs +77 -0
  80. package/web/app/shims/node-crypto.mjs +113 -0
  81. package/web/app/shims/node-fs.mjs +17 -0
  82. package/web/app/shims/node-path.mjs +19 -0
  83. package/web/app/shims/node-url.mjs +7 -0
  84. package/web/app/shims/prelude.js +58 -0
  85. package/web/app/shims/safefetch.mjs +58 -0
  86. package/web/app/shims/web-push.mjs +14 -0
  87. package/web/app/signup.mjs +248 -0
  88. package/web/app/site/_headers +9 -0
  89. package/web/app/site/_redirects +21 -0
  90. package/web/app/site/admin/admin.js +1181 -0
  91. package/web/app/site/admin/bar.css +60 -0
  92. package/web/app/site/admin/bar.js +61 -0
  93. package/web/app/site/admin/client/client.js +66 -0
  94. package/web/app/site/admin/client/index.html +43 -0
  95. package/web/app/site/admin/index.html +556 -0
  96. package/web/app/site/admin/setup/index.html +191 -0
  97. package/web/app/site/admin/setup/setup.js +308 -0
  98. package/web/app/site/admin/tokens.css +56 -0
  99. package/web/app/site/admin/window.css +55 -0
  100. package/web/app/site/admin/window.js +136 -0
  101. package/web/app/site/app/404.html +40 -0
  102. package/web/app/site/app/_headers +2 -0
  103. package/web/app/site/app/apple-touch-icon.png +0 -0
  104. package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
  105. package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
  106. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
  107. package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
  108. package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
  109. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
  110. package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
  111. package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  112. package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
  113. package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
  114. package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
  115. package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
  116. package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
  117. package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
  118. package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
  119. package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
  120. package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
  121. package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
  122. package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  123. package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  124. package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  125. package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  126. package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
  127. package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
  128. package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
  129. package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
  130. package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
  131. package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
  132. package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
  133. package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
  134. package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
  135. package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
  136. package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
  137. package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
  138. package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
  139. package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
  140. package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
  141. package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
  142. package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
  143. package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
  144. package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
  145. package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
  146. package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
  147. package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
  148. package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
  149. package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
  150. package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
  151. package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
  152. package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
  153. package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
  154. package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
  155. package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
  156. package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
  157. package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
  158. package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
  159. package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
  160. package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
  161. package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
  162. package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
  163. package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
  164. package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
  165. package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
  166. package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
  167. package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
  168. package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
  169. package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
  170. package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
  171. package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
  172. package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
  173. package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
  174. package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
  175. package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
  176. package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
  177. package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
  178. package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
  179. package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
  180. package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
  181. package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
  182. package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
  183. package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
  184. package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
  185. package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
  186. package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
  187. package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
  188. package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
  189. package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
  190. package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
  191. package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
  192. package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
  193. package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
  194. package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
  195. package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
  196. package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
  197. package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
  198. package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
  199. package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
  200. package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
  201. package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
  202. package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
  203. package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
  204. package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
  205. package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
  206. package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
  207. package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
  208. package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
  209. package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
  210. package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
  211. package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
  212. package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
  213. package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
  214. package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
  215. package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
  216. package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
  217. package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
  218. package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
  219. package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
  220. package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
  221. package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
  222. package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
  223. package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
  224. package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
  225. package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
  226. package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
  227. package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
  228. package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
  229. package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
  230. package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
  231. package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
  232. package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
  233. package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
  234. package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
  235. package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
  236. package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
  237. package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
  238. package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
  239. package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
  240. package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
  241. package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
  242. package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
  243. package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
  244. package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
  245. package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
  246. package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
  247. package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
  248. package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
  249. package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
  250. package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
  251. package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
  252. package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
  253. package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
  254. package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
  255. package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
  256. package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
  257. package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
  258. package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
  259. package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
  260. package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
  261. package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
  262. package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
  263. package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
  264. package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
  265. package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
  266. package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
  267. package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
  268. package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
  269. package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
  270. package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
  271. package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
  272. package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
  273. package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
  274. package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
  275. package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
  276. package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
  277. package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
  278. package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
  279. package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
  280. package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  281. package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  282. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
  283. package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
  284. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
  285. package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
  286. package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
  287. package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
  288. package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
  289. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
  290. package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
  291. package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
  292. package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
  293. package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
  294. package/web/app/site/app/compose/index.html +20 -0
  295. package/web/app/site/app/favicon.ico +0 -0
  296. package/web/app/site/app/index.html +73 -0
  297. package/web/app/site/app/logo-192.png +0 -0
  298. package/web/app/site/app/logo-512.png +0 -0
  299. package/web/app/site/app/logo-badge-72.png +0 -0
  300. package/web/app/site/app/logo-maskable-512.png +0 -0
  301. package/web/app/site/app/logo-monochrome-512.png +0 -0
  302. package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
  303. package/web/app/site/app/manifest.webmanifest +1 -0
  304. package/web/app/site/app/og-image-2.jpg +0 -0
  305. package/web/app/site/app/og-image.png +0 -0
  306. package/web/app/site/app/robots.txt +2 -0
  307. package/web/app/site/app/version.json +1 -0
  308. package/web/app/site/boot.js +34067 -0
  309. package/web/app/site/index.html +203 -0
  310. package/web/app/site/sw.js +53745 -0
  311. package/web/app/sw-src.mjs +205 -0
  312. package/web/front/#new-account.html# +43 -0
  313. package/web/front/admin.html +1 -81
  314. package/web/front/admin.js +85 -0
  315. package/web/front/new-account.html +1 -9
  316. package/web/front/new-account.js +13 -0
  317. package/web/front/run.html +1 -92
  318. package/web/front/run.js +107 -0
  319. package/lib/podrdf.mjs +0 -132
package/web/admin/bar.css CHANGED
@@ -4,18 +4,9 @@
4
4
  page's left edge and the buttons over its right rather than out at the window
5
5
  borders. `width: 100%` with `box-sizing: border-box` keeps the padding inside
6
6
  that 960 instead of adding to it, which is what makes the two margins equal. */
7
- /* Phanpy's own colours, read off the running client. --surface is the one you
8
- see behind Phanpy its --bg-faded-color and that is the PAGE. --raised is
9
- Phanpy's --bg-color, and that is the bar sitting on top of it. Declared here
10
- because bar.css is on all three pages, so they cannot drift. */
11
- :root { --surface: #f0f2f5; --raised: #fff;
12
- --ink: #1b1c1e; --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
13
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
14
- @media (prefers-color-scheme: dark) {
15
- :root { --surface: #18191a; --raised: #242526;
16
- --ink: #e8eaed; --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
17
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
18
- }
7
+ /* Colours come from tokens.css (the front's palette), linked by every page
8
+ before this file, so the bar matches the front and the record rather than
9
+ Phanpy: --surface is the page ground, --raised the bar band on top of it. */
19
10
  body { background: var(--surface); }
20
11
  /* The BAND spans the window; its CONTENTS stay in the 960 column. Constraining
21
12
  the element itself left the colour as a stripe with bare page either side.
@@ -51,8 +42,13 @@ body { background: var(--surface); }
51
42
  #bar select:hover { background-color: var(--field-bg-hover); }
52
43
  /* The app title, in px rather than rem: it is chrome, the same on every page,
53
44
  not something that should move when a page changes its base size. */
54
- #bar .name { flex: 1 1 auto; font-weight: 600; font-size: 24px; color: var(--heading);
45
+ #bar .name { flex: 1 1 auto; font-weight: 600; font-size: 40px; color: var(--heading);
55
46
  margin: 0; line-height: 1.1; }
47
+ /* The browser build wraps the brand text in a link (to /?add) — the general
48
+ #bar a rule below would otherwise pin it back down to nav-link size and
49
+ colour, since a rule on the anchor itself always wins over inheriting the
50
+ heading's size. */
51
+ #bar .name a { font: inherit; color: inherit; text-decoration: none; }
56
52
  /* Plain text, not chrome: three destinations in a row read better without a
57
53
  box round each. */
58
54
  #bar button, #bar a { font: inherit; font-size: .95rem; padding: .15rem .2rem;
package/web/admin/bar.js CHANGED
@@ -17,7 +17,7 @@
17
17
  // open windows apart without putting the handle in the page twice.
18
18
  // /status, not /config: an agent that is not set up yet answers this one and
19
19
  // 409s the other, which logged a failed request on every setup page.
20
- fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status').then(r => (r.ok ? r.json() : null)).then((s) => {
20
+ fetch(location.pathname.replace(/\/admin\/.*$/u, '') + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((s) => {
21
21
  if (s?.handle) document.title = `FediPod — ${s.handle}`;
22
22
  // The full fediverse handle, centred in the bar on every page.
23
23
  const el = document.getElementById('bar-handle');
@@ -32,7 +32,7 @@
32
32
  const pick = document.getElementById('actor-pick');
33
33
  if (pick && !document.getElementById('new-actor-form')) {
34
34
  const base = location.pathname.replace(/\/admin\/.*$/u, '');
35
- fetch(base + '/profiles').then(r => (r.ok ? r.json() : null)).then((j) => {
35
+ fetch(base + '/profiles', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).then((j) => {
36
36
  const actors = j?.identities || [];
37
37
  pick.textContent = '';
38
38
  const label = (r) => (r.address || r.handle || r.name)
@@ -27,7 +27,7 @@
27
27
  // "which account logged in here" is a different question, and it is the one
28
28
  // that goes wrong when the two origins get mixed.
29
29
  const base = location.pathname.replace(/\/admin\/.*$/u, '');
30
- const status = await fetch(base + '/status').then(r => (r.ok ? r.json() : null)).catch(() => null);
30
+ const status = await fetch(base + '/status', { headers: { 'x-fedipod-page': '1' } }).then(r => (r.ok ? r.json() : null)).catch(() => null);
31
31
  if (!status?.actor) return; // not set up yet; setup owns that
32
32
 
33
33
  // A deep link into the client — the record links an actor's own page this way
@@ -4,18 +4,17 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — client</title>
7
+ <link rel="stylesheet" href="../tokens.css">
7
8
  <style>
8
- :root { color-scheme: light dark; font-size: 112.5%; --link: #1a4f8a; --heading: #5b3a8e; }
9
- /* The bar is fixed height, the client takes the rest — the page never scrolls,
10
- the frame does. */
9
+ /* Colours are the shared tokens (tokens.css); only layout is here. The bar is
10
+ a fixed height, the client takes the rest — the page never scrolls, the frame
11
+ does. */
12
+ :root { color-scheme: light dark; font-size: 125%; }
11
13
  html { height: 100%; }
12
14
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; height: 100dvh;
13
15
  display: flex; flex-direction: column; overflow: hidden; }
14
16
  main { flex: 1 1 auto; min-height: 0; display: flex; }
15
17
  #client { flex: 1 1 auto; min-height: 0; width: 100%; border: 0; }
16
- @media (prefers-color-scheme: dark) {
17
- :root { --link: #7fb3e8; --heading: #b9a3e3; }
18
- }
19
18
  </style>
20
19
  <link rel="stylesheet" href="../bar.css">
21
20
  </head>
@@ -4,30 +4,14 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — record</title>
7
+ <link rel="stylesheet" href="tokens.css">
7
8
  <style>
8
- /* 20px on the root, not on body, so every rem below scales with it.
9
- Two accents, kept apart on purpose: --link is anything you can act on,
10
- --heading is structure. Using one colour for both made the headings
11
- look clickable. */
12
- /* One control chassis, one selector per element kind so nothing can fall
13
- through unstyled: every control sits in the same light well; a select
14
- carries a drawn chevron, a button carries the action-blue text. */
15
- :root { color-scheme: light dark; font-size: 112.5%;
16
- --bar-col: 1280px; /* the bar centres on the same column */
17
- --link: #1a4f8a; --heading: #5b3a8e;
18
- --ink: #1b1c1e; --muted: #4a4e53;
19
- --btn-bg: #f4efe6; --btn-bg-hover: #ece3d2; --btn-bg-active: #e2d5bd; --btn-edge: #8a7a61;
20
- --field-bg: #ffffff; --field-bg-hover: #f7f4ee; --field-edge: #767676;
21
- --danger: #b00020; --ring: var(--link);
22
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23444444' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
23
- @media (prefers-color-scheme: dark) {
24
- :root { --link: #7fb3e8; --heading: #b9a3e3;
25
- --ink: #e8eaed; --muted: #b9bdc2;
26
- --btn-bg: #35302a; --btn-bg-hover: #423b31; --btn-bg-active: #4d4436; --btn-edge: #9a8d7a;
27
- --field-bg: #383d44; --field-bg-hover: #40464e; --field-edge: #9aa0a6;
28
- --danger: #ff8a8a;
29
- --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23dddddd' stroke-width='1.6' stroke-linecap='round'/></svg>"); }
30
- }
9
+ /* Colours are the shared tokens (tokens.css, the front's palette), so the
10
+ record, the front and the bar are one visual system; only the layout root —
11
+ the base scale and the bar's column stays here. Controls share one look: a
12
+ field is a light well with a drawn chevron, a button is the front's filled
13
+ green. */
14
+ :root { color-scheme: light dark; font-size: 125%; --bar-col: 1280px; }
31
15
  /* The page itself never scrolls: the body is exactly the window; the content
32
16
  column beside the rail is the scroll container. */
33
17
  html { height: 100%; }
@@ -95,7 +79,7 @@ h3 { margin: 1.25rem 0 .35rem; }
95
79
  #moderation label { display: inline; margin: 0 .3rem 0 .5rem; font-weight: normal; color: var(--muted); }
96
80
  p { margin: .5rem 0; }
97
81
  .sub { color: var(--muted); margin-top: 0; }
98
- fieldset { border: 1px solid #bbb; border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
82
+ fieldset { border: 1px solid var(--line); border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
99
83
  legend { padding: 0 .3rem; font-weight: 600; color: var(--heading); }
100
84
  label { display: block; margin: .75rem 0 .2rem; }
101
85
  .hint { color: var(--muted); font-size: 1rem; margin: .15rem 0 0; }
@@ -114,12 +98,12 @@ select { font: inherit; appearance: none; max-width: 100%; min-width: 0;
114
98
  background-repeat: no-repeat; background-position: right .55rem center;
115
99
  background-size: .6rem auto; }
116
100
  select:hover { background-color: var(--field-bg-hover); }
117
- /* ACTIONS: identical to the settings same well, same border, same ink,
118
- Retire included. One control look for the whole page. */
119
- button { font: inherit; padding: .45rem 1rem; border-radius: .3rem; border: 1px solid var(--field-edge);
120
- background: var(--field-bg); color: var(--ink); cursor: pointer; }
121
- button:hover { background: var(--field-bg-hover); }
122
- button:active { background: var(--field-bg-hover); }
101
+ /* ACTIONS: the front's filled-green button, one look for the whole page,
102
+ Retire included the typed-handle confirm, not a colour, is what guards the
103
+ destructive ones. */
104
+ button { font: inherit; padding: .45rem 1rem; border-radius: .3rem; border: 1px solid var(--btn);
105
+ background: var(--btn); color: var(--btn-text); cursor: pointer; }
106
+ button:hover, button:active { background: var(--btn-hover); border-color: var(--btn-hover); }
123
107
  button.primary { font-weight: 600; }
124
108
  button:disabled { opacity: .55; cursor: default; }
125
109
  /* Small buttons that sit in a line of text. */
@@ -139,12 +123,29 @@ dt.under { padding-left: 1.25rem; }
139
123
  dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
140
124
  .rows { list-style: none; padding: 0; margin: .3rem 0; }
141
125
  .rows li { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
142
- padding: .4rem 0; border-top: 1px solid #ddd; }
126
+ padding: .4rem 0; border-top: 1px solid var(--line); }
143
127
  .rows li span { flex: 1 1 18rem; overflow-wrap: anywhere; }
144
128
  .muted { color: var(--muted); }
145
129
  /* The actors dropdown rides the bar, at the bar's own text scale. */
146
130
  #bar select { font-size: .95rem; }
147
- #bsky-ctl { display: inline-flex; gap: .6rem; align-items: baseline; margin-left: .6rem; }
131
+ /* The per-account Options disclosure: only where storage is not a real choice
132
+ (the Node agent's Bluesky, cross-post instead) — a summary opening
133
+ Show/Hide in Feed, Cross-post On/Off, Connected/Disconnect. Where storage
134
+ IS a choice, two dropdowns sit in the row directly instead (admin.js
135
+ connectionControls). */
136
+ details.opts { display: inline-block; }
137
+ details.opts > summary { cursor: pointer; color: var(--link); list-style: none; }
138
+ details.opts > summary::-webkit-details-marker { display: none; }
139
+ details.opts > summary::after { content: ' \25be'; }
140
+ details.opts[open] > summary::after { content: ' \25b4'; }
141
+ details.opts > div { margin: .4rem 0 .2rem; padding: .5rem .6rem; display: flex; flex-direction: column;
142
+ gap: .4rem; align-items: stretch; border: 1px solid var(--line); border-radius: .3rem;
143
+ background: var(--raised); min-width: 11rem; }
144
+ details.opts > div select { width: 100%; }
145
+ .opt-hint { color: var(--muted); font-size: .9rem; margin: -.1rem 0 .1rem; }
146
+ /* The connection and storage dropdowns that sit in the row itself (admin.js
147
+ connectionControls) — extra room from the account name before them. */
148
+ .conn-controls { display: inline-flex; align-items: center; gap: .4rem; margin-left: 1rem; }
148
149
  /* The alias row inside the Transfer-an-account-here panel. */
149
150
  #alias-ctl { display: flex; gap: .5rem; align-items: center; margin-top: .3rem; }
150
151
  #alias-ctl input { flex: 1 1 12rem; width: auto; }
@@ -155,15 +156,15 @@ dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
155
156
  below the control. */
156
157
  .choice { display: block; margin: .35rem 0; font-weight: normal; }
157
158
  .choice input { width: auto; margin-right: .4rem; }
158
- .warn { border-left: .25rem solid #c60; padding-left: .75rem; }
159
+ .warn { border-left: .25rem solid var(--warn); padding-left: .75rem; }
159
160
  .err { border-left: .25rem solid var(--danger); padding-left: .75rem; color: var(--danger); }
160
- .ok { border-left: .25rem solid #2a7; padding-left: .75rem; }
161
+ .ok { border-left: .25rem solid var(--ok); padding-left: .75rem; }
161
162
  pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem; font-size: .9rem; }
162
163
  @media (prefers-color-scheme: dark) {
163
164
  fieldset { border-color: #555; } .rows li { border-color: #444; }
164
165
  }
165
166
  </style>
166
- <link rel="stylesheet" href="bar.css">
167
+ <link rel="stylesheet" href="bar.css?v=3">
167
168
  <link rel="stylesheet" href="window.css">
168
169
  </head>
169
170
  <body>
@@ -309,13 +310,6 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
309
310
  <span id="ident-ctl" hidden>
310
311
  <button type="button" id="ident-open" class="inline" title=" Connect an account you hold on another network, so its timeline joins the one you read here">Connect an identity</button>
311
312
  </span>
312
- <span id="bsky-ctl" hidden>
313
- <select id="bsky-crosspost" aria-label="Bluesky crossposting" title=" Whether your public posts also go to the Bluesky account">
314
- <option value="on">crosspost on</option>
315
- <option value="off">crosspost off</option>
316
- </select>
317
- <button type="button" id="bsky-disconnect" class="inline danger" title=" Forget the Bluesky session and remove the stored credential">Disconnect</button>
318
- </span>
319
313
  </section>
320
314
 
321
315
  <span id="gateway-ctl" hidden>
@@ -470,7 +464,7 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
470
464
  Keep my pod-based handle: <strong id="gw-pod-preview"></strong><br>
471
465
  <span class="hint">Your address stays tied to your own pod — you can drop the
472
466
  gateway any time and keep everything.</span></label>
473
- <label class="choice"><input type="radio" name="gwShape" value="front">
467
+ <label class="choice" id="gw-shape-front"><input type="radio" name="gwShape" value="front">
474
468
  Create a handle at the gateway:
475
469
  @<input type="text" id="gw-name" placeholder="name" autocomplete="off"
476
470
  autocapitalize="off" spellcheck="false">@<span id="gw-front-host">fedipod.net</span><br>
@@ -498,6 +492,15 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
498
492
  <div id="warn-rotate-key" class="warn" hidden>
499
493
  <p>A new keypair replaces the one in this home and the actor is republished so other
500
494
  servers learn it. Any other device still holding the old key stops being able to sign.</p>
495
+ <!-- Browser build only: there the key on the pod is locked under the account
496
+ password, so a new one has to be locked too. The Node agent keeps its key
497
+ on disk and needs nothing here — stage-site.mjs reveals this row. -->
498
+ <div id="rotate-pw-row" hidden>
499
+ <label for="rotate-password">Your account password</label>
500
+ <input type="password" id="rotate-password" autocomplete="current-password">
501
+ <p class="hint">Your signing key is kept on your pod locked under this, so the
502
+ replacement has to be locked under it too. It is not sent anywhere else.</p>
503
+ </div>
501
504
  </div>
502
505
  <div id="warn-retire" class="warn" hidden>
503
506
  <p><strong>This cannot be undone.</strong> A Delete goes to every follower's inbox telling
@@ -513,28 +516,13 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
513
516
  <label for="confirm-handle">Type the handle to confirm</label>
514
517
  <input type="text" id="confirm-handle" autocomplete="off">
515
518
  </div>
516
- <div id="warn-move-state" class="warn" hidden>
517
- <p>Your timeline, contacts, blocklist and notifications move to the place you choose.
518
- Everything is copied and checked before the agent switches over, and the old copy is
519
- left where it was for you to delete. The agent keeps running.</p>
520
- <fieldset>
521
- <legend>Where to</legend>
522
- <label class="choice"><input type="radio" name="stateWhere" value="path" checked>
523
- another place on this device</label>
524
- <label class="choice"><input type="radio" name="stateWhere" value="url">
525
- another device</label>
526
- <label class="choice"><input type="radio" name="stateWhere" value="pod">
527
- onto the pod</label>
528
- <div id="state-row-path">
529
- <label for="state-path">Directory</label>
530
- <input type="text" id="state-path" autocomplete="off" placeholder="~/somewhere/private/">
531
- </div>
532
- <div id="state-row-url" hidden>
533
- <label for="state-url">Address the other device serves</label>
534
- <input type="url" id="state-url" autocomplete="off" placeholder="https://other-device.example/private/">
535
- </div>
536
- </fieldset>
537
- </div>
519
+ <!-- "Move private data" (POST /state-move) had a panel here and no way to
520
+ open it: nothing in this page has ever carried data-confirm="move-state",
521
+ so the panel was only ever hidden. Removed 2026-09-09 rather than left as
522
+ markup that looks like a feature. The route is still there for the CLI,
523
+ which is where moving the private half between a directory and the pod
524
+ belongs — it is about filesystem paths and credential.json, neither of
525
+ which a browser has. -->
538
526
  <div id="warn-move" class="warn" hidden>
539
527
  <p>A <code>Move</code> goes to every follower's inbox and their servers migrate them to the
540
528
  account you name, so they end up following that one instead. The actor left behind
@@ -4,9 +4,10 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>FediPod — setup</title>
7
+ <link rel="stylesheet" href="../tokens.css">
7
8
  <style>
8
9
  /* 20px on the root, not on body, so every rem below scales with it. */
9
- :root { color-scheme: light dark; font-size: 112.5%; --heading: #1a4f8a; }
10
+ :root { color-scheme: light dark; font-size: 125%; --heading: #1a4f8a; }
10
11
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; }
11
12
  /* The bar spans the window; the form stays in a centred column under it. */
12
13
  /* The same 960 column the bar and the record use, so the three pages line up. */
@@ -17,7 +17,7 @@ const show = (id) => { for (const s of document.querySelectorAll('section[id^="p
17
17
  // the page's own address, so one build serves both.
18
18
  const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
19
19
  const api = async (path, init) => {
20
- const res = await fetch(BASE + path, init);
20
+ const res = await fetch(BASE + path, { ...init, headers: { ...(init?.headers || {}), 'x-fedipod-page': '1' } });
21
21
  return { status: res.status, json: await res.json().catch(() => null) };
22
22
  };
23
23
  const postJson = (path, body) => api(path, {
@@ -0,0 +1,56 @@
1
+ /* tokens.css — the FediPod design tokens: the front page's palette
2
+ (web/app/index.html), so the front, the record page, the bar and the client
3
+ shell are one visual system. The front is the reference; these values mirror
4
+ its :root. Declared under every name the pages already use — plus aliases
5
+ (--ink=--fg, --muted=--sub, --link=--accent, --field-edge=--field-border,
6
+ --danger=--err, --ring=--focus) — so a page only has to drop its own :root
7
+ colours and inherit these. */
8
+ :root {
9
+ color-scheme: light dark;
10
+
11
+ /* ground + text */
12
+ --bg: #ffffff; --fg: #1a1a1a; --ink: #1a1a1a;
13
+ --surface: #f0f2f5; /* the page ground behind cards */
14
+ --raised: #ffffff; /* cards, the rail, the bar band, dialogs */
15
+
16
+ /* structure + action */
17
+ --heading: #123f74; --accent: #12467e; --link: #12467e;
18
+ --sub: #565656; --muted: #565656; --line: #b0b0b0;
19
+
20
+ /* fields */
21
+ --field-bg: #ececec; --field-bg-hover: #e2e4e8; --field-hover: #e2e4e8;
22
+ --field-text: #141414; --field-edge: #6f6f6f; --field-border: #6f6f6f;
23
+
24
+ /* buttons — filled, muted forest green (like the front) */
25
+ --btn: #3a5f43; --btn-hover: #30503a; --btn-text: #ffffff;
26
+ --btn-bg: #ececec; --btn-bg-hover: #e2e4e8; --btn-bg-active: #d8dade; --btn-edge: #6f6f6f;
27
+
28
+ /* status + focus */
29
+ --err: #b00020; --danger: #b00020; --warn: #8a4b00; --ok: #1f7a3d;
30
+ --focus: #12467e; --ring: #12467e;
31
+
32
+ --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23124f8a' stroke-width='1.6' stroke-linecap='round'/></svg>");
33
+ }
34
+
35
+ @media (prefers-color-scheme: dark) {
36
+ :root {
37
+ --bg: #191919; --fg: #f3f3f3; --ink: #f3f3f3;
38
+ --surface: #18191a; --raised: #242526;
39
+
40
+ --heading: #8fc2f2; --accent: #8fc2f2; --link: #8fc2f2;
41
+ --sub: #d2d2d2; --muted: #d2d2d2; --line: #6d6d6d;
42
+
43
+ --field-bg: #474747; --field-bg-hover: #515151; --field-hover: #515151;
44
+ --field-text: #f5f5f5; --field-edge: #9aa0a6; --field-border: #9aa0a6;
45
+
46
+ /* a touch brighter on the dark page so white text and the fill-vs-page edge
47
+ both clear their contrast floors — still muted (kept in sync with the front) */
48
+ --btn: #4a7a54; --btn-hover: #3f6a49; --btn-text: #ffffff;
49
+ --btn-bg: #474747; --btn-bg-hover: #515151; --btn-bg-active: #5b5b5b; --btn-edge: #9aa0a6;
50
+
51
+ --err: #ff9b9b; --danger: #ff9b9b; --warn: #ffb366; --ok: #7fd39a;
52
+ --focus: #8fc2f2; --ring: #8fc2f2;
53
+
54
+ --chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M1 1l4 4 4-4' fill='none' stroke='%23bcd8f0' stroke-width='1.6' stroke-linecap='round'/></svg>");
55
+ }
56
+ }
@@ -0,0 +1,77 @@
1
+ # web/app — FediPod in the browser
2
+
3
+ The in-browser build: sign up and run a FediPod account in a tab, no install.
4
+ See `claude/plans/browser-agent.md` for the whole design and status.
5
+
6
+ | file | what |
7
+ |---|---|
8
+ | `pod-auth.mjs` | The pod side of sign-in, browser-native: create a CSS account + pod, mint a client credential, and a DPoP-bound `fetch` that writes to the pod. The twin of `lib/account.mjs` + `vendor/idp-grant.cjs`. |
9
+ | `keystore.mjs` | WebCrypto RSA/Ed25519 key generation, and wrapping the keys under the account password (PBKDF2-SHA256 + AES-GCM-256). The pod holds only the wrapped form, so the pod's host cannot sign as you. |
10
+ | `keys-browser.mjs` | Importing a keys record for signing, and finding one: this browser's opened copy in IndexedDB first, else the pod's. A wrapped one the browser has not opened yet raises `KeyPasswordNeeded`, which `boot.mjs` answers with the unlock pane — once per browser. |
11
+ | `signup.mjs` | The `fedipod setup` flow, in the browser, up to publish: account, pod, credential, keys locked on the pod (owner-only ACL written *before* the key). Produces the credential/keys/config shapes the agent already reads. |
12
+ | `shims/fedify-sig.mjs` | Browser stand-in for `@fedify/fedify/sig` (which will not bundle for a browser). `sign()` returns signed headers as data for the relay; `signRequest()` wraps it Fedify-shaped. Proven byte-identical to Fedify. |
13
+ | `shims/node-crypto.mjs` | Browser stand-in for `node:crypto` — the small synchronous slice the agent uses, via crypto-browserify, plus native WebCrypto. |
14
+ | `shims/safefetch.mjs` | Browser stand-in for `lib/safefetch.mjs`. Pinning and the private-address checks are unnecessary here (a browser closes DNS rebinding itself); the BYTE BUDGET is not, so `readCapped` streams and stops at the cap exactly as the Node one does. |
15
+ | `dist/` | The bundled agent, built by `scripts/build-app.mjs`. Committed like `phanpy/dist`, regenerated on release. (Not present until the agent entry is built.) |
16
+
17
+ Build: `node scripts/build-app.mjs`. Tests live in `claude/validation/`:
18
+ `signup-browser/`, `signing-shim/`, `agent-bundle/`.
19
+
20
+ ## The agent (built 2026-09-07)
21
+
22
+ | file | what |
23
+ |---|---|
24
+ | `pod-remote.mjs` | RemotePod over the DPoP session — the agent's pod I/O, ACLs and deletion deny-list, browser-native. |
25
+ | `keys-browser.mjs` | Load the signing key into WebCrypto. |
26
+ | `deliver-relay.mjs` | lib's Deliverer, sending through the relay (a browser cannot set Date/Host). |
27
+ | `agent.mjs` | Wires lib's store, publisher, intake and Mastodon facade with the browser edges. |
28
+ | `sw-src.mjs` | The service worker that hosts the agent and answers the facade (Mastodon paths + the owner/manage paths); static files fall through. Built to `dist/sw.js`. |
29
+ | `boot.mjs` | Page side: sign up / sign in, register the worker, hand it the boot material, route into `/admin/client/`; `?signout` / `?add` teardown. Built to `dist/boot.js`. |
30
+ | `shims/` | Browser stand-ins the bundle needs: `node-crypto`, `node-path`, `node-url`, `node-fs`, `web-push`, `safefetch`, `prelude` (process + Buffer). |
31
+
32
+ ## The manage surface & connected accounts (built 2026-09-08)
33
+
34
+ The owner's record/manage page is the SAME `web/admin` UI the Node agent serves,
35
+ reused as static files under `/admin/` (staged by `scripts/stage-site.mjs`); the
36
+ worker answers the data endpoints it calls. See
37
+ `claude/plans/browser-manage-surface.md` for the whole design and status, and
38
+ `claude/plans/post-as-connected-account.md` for the deferred "post as" plan.
39
+
40
+ | file | what |
41
+ |---|---|
42
+ | `admin-facade.mjs` | Answers the owner/manage endpoints (`/status`, `/config`, `/gateway`, `/alias`, `/rotate-key`, `/rebuild`, `/move`, `/retire`, `/inbox/prune`, `/deadletter`, `/blocks`, `/atproto*`, `/fediacct*`) over the browser agent, mirroring `lib/admin.mjs`. Personal only. |
43
+ | `atproto-browser.mjs` | The Bluesky connection (`lib/atproto.mjs`) with a per-connection storage choice — IndexedDB (this browser) or owner-only pod state — plus pause. |
44
+ | `fediacct-browser.mjs` | Connections to fediverse accounts on other servers (`lib/fediacct.mjs`): the OAuth dance, the same storage choice, and a best-effort server-side revoke on disconnect. |
45
+ | `idb-kv.mjs` | The per-origin IndexedDB key/value store the two connectors use for "on this device, never the pod" credentials. |
46
+
47
+ `agent.mjs` also holds the single-active-agent **lease** (`lib/lease.mjs`) — a
48
+ second device runs read-only until it takes over — and starts the drain/mirrors
49
+ only when it is the active holder.
50
+
51
+ Build: `node scripts/build-app.mjs --entry web/app/sw-src.mjs --out web/app/dist/sw.js` and `--entry web/app/boot.mjs --out web/app/dist/boot.js`, then `node scripts/stage-site.mjs` to assemble `web/app/site/` (the front, the worker, Phanpy under `/app/`, and `web/admin` under `/admin/`).
52
+
53
+ Tests: `claude/smoke-tests/admin-facade-smoke.mjs`, and eight harnesses in
54
+ `claude/validation/browser-agent/` that drive a real Chrome against a scratch
55
+ Solid server — `run` (the agent and the facade), `sw-run` (the same through the
56
+ service worker, plus media upload and notification paging), `full-run` (the
57
+ staged site, sign-up to Phanpy rendering), `unlock-run` (a browser with no opened
58
+ key), `oidc-run`, `resume-run`, `gateway-run`, and `provider-csp-run` (the
59
+ sign-up page's own policy, read from the browser's refusal reports). Three
60
+ helpers sit beside them: `idp-login.mjs` fills the pod's login and consent
61
+ screens, `worker-log.mjs` reads the agent's console out of the service worker,
62
+ and the attach and relay stubs in `sw-run.mjs` stand in for the front.
63
+
64
+ ## What this build does not have
65
+
66
+ Declared rather than pretended: each is omitted from the instance document, so
67
+ a client hides the control instead of offering one that quietly does nothing.
68
+ See the `MastoApi` options in `agent.mjs`.
69
+
70
+ | | why |
71
+ |---|---|
72
+ | **Streaming** | a service worker answers fetches, not sockets. No streaming URL is advertised, so clients poll. |
73
+ | **Web push** | `shims/web-push.mjs` is a no-op. `vapid` is omitted, and a client that subscribes anyway gets a 422 rather than a subscription nothing will push to. |
74
+ | **Scheduled posts** | nothing runs between now and the scheduled time. A `scheduled_at` is refused with a 422 that says so — accepting one was silent loss. |
75
+ | **Groups** | sign-up makes personal identities only (`signup.mjs`), and the moderation surface is not here. Joining a group works; hosting one needs the installed agent. See `groups.md`. |
76
+ | **A fronted `@you@front` handle** | the browser model is `@you@yourpod` with the gateway as a mail door. `admin-facade.mjs` refuses a fronted attach, and `stage-site.mjs` hides the radio that offered it. |
77
+ | **Moving the private half** | `/state-move` is about filesystem paths and `credential.json`. A browser has neither; its private half is always on the pod. |