@hienlh/ppm 0.18.2 → 0.18.4

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 (517) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +6 -1
  5. package/dist/web/assets/{ai-resource-editor-B6CODu6u.js → ai-resource-editor-Bb-NNlp1.js} +1 -1
  6. package/dist/web/assets/{angular-html-C-Kjr43S.js → angular-html-jfMqUuRd.js} +1 -1
  7. package/dist/web/assets/{angular-ts-CaDDbZqm.js → angular-ts-wytn9PMm.js} +1 -1
  8. package/dist/web/assets/api-client-CzABTreM.js +1 -0
  9. package/dist/web/assets/{api-settings-CAhcNpj5.js → api-settings-DfNRPr1q.js} +1 -1
  10. package/dist/web/assets/api-settings-DlcoR5QT.js +1 -0
  11. package/dist/web/assets/{api-themes-CAjg_Bt9.js → api-themes-CEyYK7AK.js} +1 -1
  12. package/dist/web/assets/{apl-B1WsQFAb.js → apl-i-ahKx6O.js} +1 -1
  13. package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +1 -0
  14. package/dist/web/assets/arrow-right-DubGFkN8.js +1 -0
  15. package/dist/web/assets/{astro-Bdnz7nVp.js → astro-DVBCJfG6.js} +1 -1
  16. package/dist/web/assets/audio-preview-DG2Tw0cO.js +1 -0
  17. package/dist/web/assets/{blade-B2euh8sS.js → blade-BHlL8SqU.js} +1 -1
  18. package/dist/web/assets/{bsl-GNeMaxd0.js → bsl-D-UOfR1F.js} +1 -1
  19. package/dist/web/assets/button-B4MWPjWj.js +1 -0
  20. package/dist/web/assets/{cairo-D7G0W7-s.js → cairo-Bn6rucli.js} +1 -1
  21. package/dist/web/assets/chat-tab-CgAv-crh.js +15 -0
  22. package/dist/web/assets/{cobol-Q6YgysnI.js → cobol-Byn9FYKu.js} +1 -1
  23. package/dist/web/assets/code-editor-bX4jlgMy.js +10 -0
  24. package/dist/web/assets/{coffee-BjufWxVc.js → coffee-DjBl4gm-.js} +1 -1
  25. package/dist/web/assets/{conflict-editor-DnSltcyC.js → conflict-editor-B6hVffX3.js} +3 -3
  26. package/dist/web/assets/{context-menu-q-RHHAWz.js → context-menu-DkQdt6YA.js} +1 -1
  27. package/dist/web/assets/{cpp-CqWU0gCR.js → cpp-CKzic-3E.js} +1 -1
  28. package/dist/web/assets/{crystal-ylayVajF.js → crystal-Bkg-Ly_g.js} +1 -1
  29. package/dist/web/assets/{csv-preview-DnaFdgOB.js → csv-preview-hL4Z9HS5.js} +1 -1
  30. package/dist/web/assets/{data-grid-overlay-editor-BzPfsCfd.js → data-grid-overlay-editor-B1mTl3KS.js} +1 -1
  31. package/dist/web/assets/{database-viewer-C7uLy8Z1.js → database-viewer-CTK5kMaA.js} +1 -1
  32. package/dist/web/assets/diff-viewer-DZxnANY4.js +4 -0
  33. package/dist/web/assets/{docx-preview-Dd2nTW2v.js → docx-preview-CrAc7Qjv.js} +1 -1
  34. package/dist/web/assets/download-D9UYwY0-.js +1 -0
  35. package/dist/web/assets/{edge-DX4NxQfh.js → edge-DVbZnd3Y.js} +1 -1
  36. package/dist/web/assets/{edit-diff-preview-DfX-9om9.js → edit-diff-preview-HTZPC9VP.js} +1 -1
  37. package/dist/web/assets/{elixir-CSh3NjbV.js → elixir-Dwi8Wu7W.js} +1 -1
  38. package/dist/web/assets/{elm-DBzwFYq9.js → elm-DtIv-HXJ.js} +1 -1
  39. package/dist/web/assets/{erb-yxVOb8aB.js → erb-D3HEDz1x.js} +1 -1
  40. package/dist/web/assets/{erlang-5XIDkYrM.js → erlang-2MjTCzGz.js} +1 -1
  41. package/dist/web/assets/{esm-DO4el2OJ.js → esm-CZ8B8Ba2.js} +1 -1
  42. package/dist/web/assets/explorer-body-DykJXCTw.js +2 -0
  43. package/dist/web/assets/explorer-window-content-NS8Y0v8c.js +1 -0
  44. package/dist/web/assets/{extension-webview-Dk_zyoXZ.js → extension-webview-D6eywKPN.js} +1 -1
  45. package/dist/web/assets/{fortran-fixed-form-D4tE5VWY.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
  46. package/dist/web/assets/{fsharp-By8soj6-.js → fsharp-Bxbn_gbr.js} +1 -1
  47. package/dist/web/assets/{gdresource-DoSwM7Hv.js → gdresource-sFMY4D2Q.js} +1 -1
  48. package/dist/web/assets/{git-commit-_ZiciHIx.js → git-commit-BzB9Lio5.js} +1 -1
  49. package/dist/web/assets/{git-log-panel-BLQSy6rC.js → git-log-panel-DAogZ9QV.js} +1 -1
  50. package/dist/web/assets/{git-rebase-C3Y5N9-s.js → git-rebase-CKSVd848.js} +1 -1
  51. package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +1 -0
  52. package/dist/web/assets/glide-data-grid-DxfJRN-r.js +138 -0
  53. package/dist/web/assets/{glimmer-js-BPBTXVlC.js → glimmer-js-BY8aExAJ.js} +1 -1
  54. package/dist/web/assets/{glimmer-ts-BQSZdy_D.js → glimmer-ts-Bak54dBX.js} +1 -1
  55. package/dist/web/assets/{glsl-CbQdxpU-.js → glsl-nR2eaJQe.js} +1 -1
  56. package/dist/web/assets/{graphql-GMlKikpY.js → graphql-CGPPFAU6.js} +1 -1
  57. package/dist/web/assets/group-chat-tab-DkVi6qL2.js +1 -0
  58. package/dist/web/assets/{hack-DyshcLec.js → hack-CXIMX-Ol.js} +1 -1
  59. package/dist/web/assets/{haml-rqCqWzsq.js → haml-tPY-9Pfm.js} +1 -1
  60. package/dist/web/assets/{handlebars-BCKwSWjR.js → handlebars-BuW1LT7T.js} +1 -1
  61. package/dist/web/assets/{html-Cg29xPR1.js → html-bgVcwZ-t.js} +1 -1
  62. package/dist/web/assets/{html-derivative-geltIVg8.js → html-derivative-D6-IxTgn.js} +1 -1
  63. package/dist/web/assets/{http-pG7Vp237.js → http-CJ1zyB-h.js} +1 -1
  64. package/dist/web/assets/{hurl-Di3Uu_Mc.js → hurl-CmoT7l3T.js} +1 -1
  65. package/dist/web/assets/{hxml-CRy4lbv8.js → hxml-CztOsL8o.js} +1 -1
  66. package/dist/web/assets/{image-preview-KubLSLNO.js → image-preview-Byjx8MoL.js} +1 -1
  67. package/dist/web/assets/index-Bucbpby-.css +2 -0
  68. package/dist/web/assets/index-CcN0TBm4.js +52 -0
  69. package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +1 -0
  70. package/dist/web/assets/{jinja-C8GyVnvd.js → jinja-B1pq3jJq.js} +1 -1
  71. package/dist/web/assets/{jison-BywnqPn6.js → jison-D8B5pCwA.js} +1 -1
  72. package/dist/web/assets/{julia-DTrO3T3B.js → julia-D0K3bwpU.js} +1 -1
  73. package/dist/web/assets/{just-CA3KSHPi.js → just-B9Jlddu-.js} +1 -1
  74. package/dist/web/assets/keybindings-store-DJIHqbLM.js +1 -0
  75. package/dist/web/assets/{latex-BC5xUMpc.js → latex-BPJSSE6j.js} +1 -1
  76. package/dist/web/assets/{liquid-Chd8tSyQ.js → liquid-lv10fo4U.js} +1 -1
  77. package/dist/web/assets/{lua-DHaEtwLv.js → lua-PNuaIBEF.js} +1 -1
  78. package/dist/web/assets/{markdown-renderer-B4uL6GVA.js → markdown-renderer-CXlEe3ma.js} +3 -3
  79. package/dist/web/assets/markdown-renderer-pYznJDbh.js +1 -0
  80. package/dist/web/assets/{marko-Desx_bC-.js → marko-Du3frD6X.js} +1 -1
  81. package/dist/web/assets/{mdc-DahWHc1t.js → mdc-BeDVcFRl.js} +1 -1
  82. package/dist/web/assets/media-url-C5vA0ha9.js +1 -0
  83. package/dist/web/assets/{mobile-bottom-sheet-BQt45q9T.js → mobile-bottom-sheet-CS3XMwFw.js} +1 -1
  84. package/dist/web/assets/mobile-explorer-sheet-BuqBlP3o.js +1 -0
  85. package/dist/web/assets/{nextflow-3A7lutCA.js → nextflow-DkcXP71s.js} +1 -1
  86. package/dist/web/assets/{nginx-XQda6Hdy.js → nginx-C5n8N_bt.js} +1 -1
  87. package/dist/web/assets/{nim-DHqlpwP1.js → nim-CIhyfs-A.js} +1 -1
  88. package/dist/web/assets/notification-store-Bcl5efY2.js +1 -0
  89. package/dist/web/assets/{number-overlay-editor-DgZtViPO.js → number-overlay-editor-NgOsWJ7x.js} +1 -1
  90. package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +1 -0
  91. package/dist/web/assets/{panel-store-s6Qx49RK.js → panel-store-DCD82zc1.js} +1 -1
  92. package/dist/web/assets/{pdf-preview-xZQVUtaV.js → pdf-preview-F2DWVB85.js} +1 -1
  93. package/dist/web/assets/{perl-M-ncjACb.js → perl-Cf31sZYE.js} +1 -1
  94. package/dist/web/assets/{php-CKIajrZi.js → php-Dzl_MDIW.js} +1 -1
  95. package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +1 -0
  96. package/dist/web/assets/plus-DmjC2iRB.js +1 -0
  97. package/dist/web/assets/{postgres-viewer-Ds0FxBel.js → postgres-viewer-KZqPYfOB.js} +3 -3
  98. package/dist/web/assets/{project-store-DyaEpHVG.js → project-store-B8BkHXy5.js} +1 -1
  99. package/dist/web/assets/{pug-DO0u_gO4.js → pug-CxSYHrB3.js} +1 -1
  100. package/dist/web/assets/{qml-UEje1PAA.js → qml-DlEjGhwN.js} +1 -1
  101. package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +1 -0
  102. package/dist/web/assets/{razor-lCs5T1Ib.js → razor-KVt5uW14.js} +1 -1
  103. package/dist/web/assets/{rst-CZX_O0z4.js → rst-D5BwNTnw.js} +1 -1
  104. package/dist/web/assets/{ruby-idycVAyE.js → ruby-D5fejQwd.js} +1 -1
  105. package/dist/web/assets/run-in-terminal-B8TDtaM_.js +1 -0
  106. package/dist/web/assets/{sas-o5gTfUwp.js → sas-Bj7B1CB2.js} +1 -1
  107. package/dist/web/assets/{scss-CRTk3iBQ.js → scss-BB0CUoCL.js} +1 -1
  108. package/dist/web/assets/send-to-chat-CuBAVC8l.js +1 -0
  109. package/dist/web/assets/{settings-store-ChqHdT2V.js → settings-store-CY-4_U3H.js} +2 -2
  110. package/dist/web/assets/settings-tab-Khj-w6Sr.js +1 -0
  111. package/dist/web/assets/{shaderlab-3e5ln8ci.js → shaderlab-BdjEZUMc.js} +1 -1
  112. package/dist/web/assets/{shellsession-CGQjQ75I.js → shellsession-Bp1OA1rR.js} +1 -1
  113. package/dist/web/assets/{soy-CxY0VwX2.js → soy-DlYz3wsC.js} +1 -1
  114. package/dist/web/assets/{sparql-CBAGAFuq.js → sparql-CdfII5Jd.js} +1 -1
  115. package/dist/web/assets/sql-query-editor-Cp0_e2P1.js +1 -0
  116. package/dist/web/assets/{sqlite-viewer-CZYDknNa.js → sqlite-viewer-CIv3sagZ.js} +1 -1
  117. package/dist/web/assets/{stata-BoezhOvk.js → stata-BvUA6GUW.js} +1 -1
  118. package/dist/web/assets/{surrealql-CHEzAhu7.js → surrealql--DLNbTyI.js} +1 -1
  119. package/dist/web/assets/{svelte-y6xtu2zX.js → svelte-CBfSsG4n.js} +1 -1
  120. package/dist/web/assets/system-monitor-tab-CrssTOf4.js +1 -0
  121. package/dist/web/assets/{tab-store-C42sktPo.js → tab-store-DAsvpx4t.js} +1 -1
  122. package/dist/web/assets/team-member-window-content-D-hXax3c.js +1 -0
  123. package/dist/web/assets/{templ-DbG2y5yD.js → templ-Dv8zpKZW.js} +1 -1
  124. package/dist/web/assets/terminal-tab-BIQDG-Ti.js +3 -0
  125. package/dist/web/assets/{tex-CeKQhrQG.js → tex-CsrOiqh1.js} +1 -1
  126. package/dist/web/assets/tool-cards-Dx-Rrpri.js +8 -0
  127. package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +1 -0
  128. package/dist/web/assets/{ts-tags-iKKOXJjG.js → ts-tags-3T98k4EJ.js} +1 -1
  129. package/dist/web/assets/{twig-DK8Vrp-G.js → twig-u6QZ45FH.js} +1 -1
  130. package/dist/web/assets/{use-blob-url-DYtOHK7u.js → use-blob-url-BmlYYZ8L.js} +1 -1
  131. package/dist/web/assets/use-is-mobile-B8FA4kQf.js +1 -0
  132. package/dist/web/assets/use-monaco-theme-Djp1_fKi.js +1 -0
  133. package/dist/web/assets/use-websocket-CCYA9-a2.js +1 -0
  134. package/dist/web/assets/{vendor-mermaid-C977xNeE.js → vendor-mermaid-Bg4vE8QA.js} +1 -1
  135. package/dist/web/assets/video-preview-cvjNMjWA.js +1 -0
  136. package/dist/web/assets/{vue-JkIEObMV.js → vue-CxIsxtwn.js} +1 -1
  137. package/dist/web/assets/{vue-html-D8SqnU86.js → vue-html-BGL5qnuH.js} +1 -1
  138. package/dist/web/assets/{vue-vine-DJj7N4WD.js → vue-vine-DHPh_gMr.js} +1 -1
  139. package/dist/web/assets/{xml-C-00w9Rv.js → xml-C-vM2m7J.js} +1 -1
  140. package/dist/web/assets/{xsl-CVdCwPHV.js → xsl-DvTXNwuO.js} +1 -1
  141. package/dist/web/index.html +21 -20
  142. package/dist/web/sw.js +1 -1
  143. package/docs/codebase-summary.md +1 -0
  144. package/package.json +1 -1
  145. package/scripts/dev-quick-tunnel-retry.mjs +51 -0
  146. package/src/providers/claude-agent-sdk.ts +6 -0
  147. package/src/server/helpers/media-route-handlers.ts +66 -0
  148. package/src/server/helpers/range-file-response.ts +86 -0
  149. package/src/server/index.ts +24 -17
  150. package/src/server/middleware/auth.ts +3 -2
  151. package/src/server/routes/files.ts +40 -7
  152. package/src/server/routes/fs-browse.ts +43 -5
  153. package/src/server/routes/fs-upload.ts +32 -0
  154. package/src/server/routes/teams.ts +34 -0
  155. package/src/server/ws/chat.ts +64 -16
  156. package/src/server/ws/team-inbox-watcher.ts +61 -22
  157. package/src/server/ws/terminal.ts +24 -6
  158. package/src/services/fs-ops/fs-ops-upload.service.ts +120 -0
  159. package/src/services/media-transcode/ffmpeg-capabilities.ts +89 -0
  160. package/src/services/media-transcode/media-probe.ts +63 -0
  161. package/src/services/media-transcode/transcode-stream.ts +129 -0
  162. package/src/services/subagent-transcript-merger.ts +1 -1
  163. package/src/services/team-member-activity/member-activity.service.ts +117 -0
  164. package/src/services/team-member-activity/outbound-message-scanner.ts +121 -0
  165. package/src/services/team-member-activity/subagent-transcript-index.ts +95 -0
  166. package/src/services/team-member-activity/transcript-tail-summary.ts +125 -0
  167. package/src/shared/background-agent-status.ts +79 -0
  168. package/src/types/api.ts +1 -0
  169. package/src/types/chat.ts +6 -1
  170. package/src/web/components/chat/chat-history-bar.tsx +4 -2
  171. package/src/web/components/chat/chat-tab.tsx +1 -0
  172. package/src/web/components/chat/message-input.tsx +13 -6
  173. package/src/web/components/chat/mini-markdown.tsx +17 -0
  174. package/src/web/components/chat/send-message-card.tsx +134 -0
  175. package/src/web/components/chat/send-message-parse.ts +159 -0
  176. package/src/web/components/chat/team-activity-panel.tsx +107 -121
  177. package/src/web/components/chat/team-member-activity-row.tsx +92 -0
  178. package/src/web/components/chat/team-member-list.tsx +43 -0
  179. package/src/web/components/chat/team-member-window-content.tsx +97 -0
  180. package/src/web/components/chat/team-message-badges.ts +18 -0
  181. package/src/web/components/chat/team-message-list.tsx +88 -0
  182. package/src/web/components/chat/team-message-preview.ts +87 -0
  183. package/src/web/components/chat/tool-cards.tsx +100 -23
  184. package/src/web/components/editor/audio-preview.tsx +6 -7
  185. package/src/web/components/editor/video-player/use-video-keyboard-shortcuts.ts +62 -0
  186. package/src/web/components/editor/video-player/video-player-controls.tsx +103 -0
  187. package/src/web/components/editor/video-player/video-player.tsx +190 -0
  188. package/src/web/components/editor/video-player/video-settings-menu.tsx +92 -0
  189. package/src/web/components/editor/video-player/video-transform.ts +49 -0
  190. package/src/web/components/editor/video-preview.tsx +75 -14
  191. package/src/web/components/explorer/file-tree.tsx +6 -0
  192. package/src/web/components/explorer/tree-node-context-menu.tsx +5 -1
  193. package/src/web/components/floating-window/window-content-registry.ts +5 -0
  194. package/src/web/components/floating-window/window-store-types.ts +1 -1
  195. package/src/web/components/os-explorer/actions/explorer-actions-upload.ts +96 -0
  196. package/src/web/components/os-explorer/actions/use-explorer-actions.ts +23 -2
  197. package/src/web/components/os-explorer/actions/use-explorer-upload-actions.tsx +41 -0
  198. package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +10 -1
  199. package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +8 -1
  200. package/src/web/components/os-explorer/dnd/use-external-file-drop.ts +84 -0
  201. package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +46 -3
  202. package/src/web/components/os-explorer/explorer-body.tsx +1 -1
  203. package/src/web/components/os-explorer/explorer-context-menu.tsx +10 -0
  204. package/src/web/components/os-explorer/explorer-dialogs.tsx +2 -0
  205. package/src/web/components/os-explorer/explorer-sidebar.tsx +10 -5
  206. package/src/web/components/os-explorer/explorer-toolbar.tsx +32 -2
  207. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +5 -1
  208. package/src/web/components/os-explorer/upload/collect-dropped-entries.ts +81 -0
  209. package/src/web/components/os-explorer/upload/upload-queue.ts +79 -0
  210. package/src/web/components/os-explorer/upload/use-upload-picker.tsx +74 -0
  211. package/src/web/components/os-explorer/views/column-row.tsx +3 -1
  212. package/src/web/components/os-explorer/views/column-view-column.tsx +6 -1
  213. package/src/web/components/os-explorer/views/icons-view-tile.tsx +1 -1
  214. package/src/web/components/os-explorer/views/icons-view.tsx +5 -1
  215. package/src/web/components/os-explorer/views/list-row.tsx +1 -1
  216. package/src/web/components/os-explorer/views/list-view.tsx +5 -1
  217. package/src/web/components/shared/markdown-code-block.tsx +3 -7
  218. package/src/web/components/terminal/terminal-tab.tsx +19 -25
  219. package/src/web/hooks/use-chat.ts +77 -15
  220. package/src/web/hooks/use-team-activity-feed.ts +92 -0
  221. package/src/web/hooks/use-terminal-command-queue.ts +108 -0
  222. package/src/web/hooks/use-terminal.ts +45 -3
  223. package/src/web/lib/fs-api.ts +9 -0
  224. package/src/web/lib/fs-upload-xhr.ts +62 -0
  225. package/src/web/lib/media-url.ts +51 -0
  226. package/src/web/lib/open-terminal-at.ts +22 -0
  227. package/src/web/lib/run-in-terminal.ts +55 -0
  228. package/src/web/lib/send-to-chat.ts +118 -0
  229. package/src/web/lib/ws-auth.ts +13 -0
  230. package/src/web/lib/ws-client.ts +10 -5
  231. package/.release-run.sh +0 -5
  232. package/dist/web/assets/api-client-CaRE4HYh.js +0 -1
  233. package/dist/web/assets/api-settings-DBcXfeJ8.js +0 -1
  234. package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +0 -1
  235. package/dist/web/assets/audio-preview-BkNAEqcB.js +0 -1
  236. package/dist/web/assets/chat-tab-B-MEsHYp.js +0 -13
  237. package/dist/web/assets/code-editor-bC1KAEUA.js +0 -10
  238. package/dist/web/assets/dialog-CkI7rwrh.js +0 -1
  239. package/dist/web/assets/diff-viewer-BwElk4q9.js +0 -4
  240. package/dist/web/assets/explorer-body-DzdXBlWw.js +0 -2
  241. package/dist/web/assets/explorer-window-content-Dgl1WTNs.js +0 -1
  242. package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +0 -1
  243. package/dist/web/assets/glide-data-grid-QZ_fgjIa.js +0 -138
  244. package/dist/web/assets/group-chat-tab-BbdVlKFv.js +0 -1
  245. package/dist/web/assets/index-D-xwjNyX.css +0 -2
  246. package/dist/web/assets/index-wd3FSHHn.js +0 -52
  247. package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +0 -1
  248. package/dist/web/assets/keybindings-store-Bp_c_C3z.js +0 -1
  249. package/dist/web/assets/markdown-renderer-2asl-3Kk.js +0 -1
  250. package/dist/web/assets/mobile-explorer-sheet-LWUDaNBV.js +0 -1
  251. package/dist/web/assets/notification-store-BasUnpt7.js +0 -1
  252. package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +0 -1
  253. package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +0 -1
  254. package/dist/web/assets/plus-9LatwJMx.js +0 -1
  255. package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +0 -1
  256. package/dist/web/assets/settings-tab-vhatQ51-.js +0 -1
  257. package/dist/web/assets/sql-query-editor-BblB2Imt.js +0 -1
  258. package/dist/web/assets/system-monitor-tab-D8dy-RT_.js +0 -1
  259. package/dist/web/assets/terminal-tab-CfD22q5x.js +0 -3
  260. package/dist/web/assets/tool-cards-DNgw1yI6.js +0 -7
  261. package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +0 -1
  262. package/dist/web/assets/use-is-mobile-DnFSkUG2.js +0 -1
  263. package/dist/web/assets/use-monaco-theme-DEbn-QT3.js +0 -1
  264. package/dist/web/assets/video-preview-DcUaFYyv.js +0 -1
  265. package/src/web/components/chat/team-activity-popover.tsx +0 -202
  266. /package/dist/web/assets/{abap-DrMyiWmK.js → abap-C1K0-wzc.js} +0 -0
  267. /package/dist/web/assets/{actionscript-3-BlSOyi0y.js → actionscript-3-CJhQVjLA.js} +0 -0
  268. /package/dist/web/assets/{ada-B_m96icB.js → ada-Bt8r9fME.js} +0 -0
  269. /package/dist/web/assets/{andromeeda-Ch3vOiZB.js → andromeeda-DF1I8qi3.js} +0 -0
  270. /package/dist/web/assets/{apache-CdsaXc1n.js → apache-CnlleE6P.js} +0 -0
  271. /package/dist/web/assets/{apex-0AnsBevk.js → apex-j0C6Xmk6.js} +0 -0
  272. /package/dist/web/assets/{api-client-rTAGdEKo.js → api-client-BSB14J4w.js} +0 -0
  273. /package/dist/web/assets/{applescript-CSxLolzy.js → applescript-DO7AnO8U.js} +0 -0
  274. /package/dist/web/assets/{apply-theme-CMQXnUAa.js → apply-theme-CNj1iA-v.js} +0 -0
  275. /package/dist/web/assets/{ara-DXZS0jA5.js → ara-i83AydZj.js} +0 -0
  276. /package/dist/web/assets/{asciidoc-DDy3pH2A.js → asciidoc-DbBA6-sF.js} +0 -0
  277. /package/dist/web/assets/{asm-Bd17hhqg.js → asm-B5S47Rgr.js} +0 -0
  278. /package/dist/web/assets/{aurora-x-C62HJRs-.js → aurora-x-De_IaSKw.js} +0 -0
  279. /package/dist/web/assets/{awk-Cc-nazni.js → awk-CfyLHXqS.js} +0 -0
  280. /package/dist/web/assets/{ayu-dark-BsfmNWCU.js → ayu-dark-DTLdZ1Si.js} +0 -0
  281. /package/dist/web/assets/{ayu-light-BZ4HLa4X.js → ayu-light-DTOfkzhu.js} +0 -0
  282. /package/dist/web/assets/{ayu-mirage-Bhyjyiqr.js → ayu-mirage-DsAECCMv.js} +0 -0
  283. /package/dist/web/assets/{ballerina-QiWgir3v.js → ballerina-CumGp9A7.js} +0 -0
  284. /package/dist/web/assets/{bat-DxtlkwN3.js → bat-Ca2SBbYN.js} +0 -0
  285. /package/dist/web/assets/{beancount-Bt21X7M_.js → beancount-CqyXBwwF.js} +0 -0
  286. /package/dist/web/assets/{berry-Cksre7lO.js → berry-yl-kJr_H.js} +0 -0
  287. /package/dist/web/assets/{bibtex-DkakgDlR.js → bibtex-DoFFszjV.js} +0 -0
  288. /package/dist/web/assets/{bicep-8crmcyXU.js → bicep-CWPi4nfK.js} +0 -0
  289. /package/dist/web/assets/{bird2-CewQ2I_Z.js → bird2-DXew-ut0.js} +0 -0
  290. /package/dist/web/assets/{bot-nk1k3ELu.js → bot-CunPuEdW.js} +0 -0
  291. /package/dist/web/assets/{c-C_6zE8Ha.js → c-clbySDvd.js} +0 -0
  292. /package/dist/web/assets/{c3-DLWB9-dp.js → c3-B65jvSM1.js} +0 -0
  293. /package/dist/web/assets/{cadence-D8qYS6pR.js → cadence-Cm8tQTHN.js} +0 -0
  294. /package/dist/web/assets/{can-open-in-ppm-BzVXoV-d.js → can-open-in-ppm-Cu-XYIgu.js} +0 -0
  295. /package/dist/web/assets/{catppuccin-frappe-Bh95lNRy.js → catppuccin-frappe-DLeGslh_.js} +0 -0
  296. /package/dist/web/assets/{catppuccin-latte-C1PlN3G0.js → catppuccin-latte-CR5GeHws.js} +0 -0
  297. /package/dist/web/assets/{catppuccin-macchiato-CldtgdK4.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
  298. /package/dist/web/assets/{catppuccin-mocha-BiwqxX9A.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
  299. /package/dist/web/assets/{clarity-CGiU22jY.js → clarity-DPulbvKH.js} +0 -0
  300. /package/dist/web/assets/{clojure-COIC3doh.js → clojure-DFuWS20u.js} +0 -0
  301. /package/dist/web/assets/{cmake-DWUEQfHv.js → cmake-DejCqcu4.js} +0 -0
  302. /package/dist/web/assets/{codeowners-BNHiAQea.js → codeowners-BwAyqWIH.js} +0 -0
  303. /package/dist/web/assets/{codeql-BuKaoS0j.js → codeql-DgSwFjqI.js} +0 -0
  304. /package/dist/web/assets/{common-lisp-D5IKbxsd.js → common-lisp-CxIVJCtf.js} +0 -0
  305. /package/dist/web/assets/{coq-BMVS-p94.js → coq-DLSjSnmf.js} +0 -0
  306. /package/dist/web/assets/{csharp-DMlbXbM4.js → csharp-C1HHzXBp.js} +0 -0
  307. /package/dist/web/assets/{css-CjkJC89X.js → css-VXsMY--y.js} +0 -0
  308. /package/dist/web/assets/{csv-DoNVBBf0.js → csv-BEbcJdHz.js} +0 -0
  309. /package/dist/web/assets/{csv-parser-DHvoTjMF.js → csv-parser-r-Nk1oHT.js} +0 -0
  310. /package/dist/web/assets/{cue-DoTRbBg-.js → cue-BkDnFqUD.js} +0 -0
  311. /package/dist/web/assets/{cypher-DVsNdTT3.js → cypher-1cMqojSQ.js} +0 -0
  312. /package/dist/web/assets/{d-EX7BBSrv.js → d-BnYjRXtH.js} +0 -0
  313. /package/dist/web/assets/{dark-plus-P_YTHwlh.js → dark-plus-XfbaH7kZ.js} +0 -0
  314. /package/dist/web/assets/{dart-Do-rgU4S.js → dart-Ca7s-8hK.js} +0 -0
  315. /package/dist/web/assets/{data-grid-types-CUx8cusN.js → data-grid-types-BXwlFMzs.js} +0 -0
  316. /package/dist/web/assets/{dax-DxOieacu.js → dax-CmuOw-mY.js} +0 -0
  317. /package/dist/web/assets/{desktop-CvdV7SBf.js → desktop-C2gJ6Ovb.js} +0 -0
  318. /package/dist/web/assets/{diff-CNOetGen.js → diff-BCEo1Ej7.js} +0 -0
  319. /package/dist/web/assets/{dist-CMfLpPW_.js → dist-CUPbqNnL.js} +0 -0
  320. /package/dist/web/assets/{dist-DduBIVkD.js → dist-DOlBnxFX.js} +0 -0
  321. /package/dist/web/assets/{docker-DAlG4_q-.js → docker-DN2jlyAh.js} +0 -0
  322. /package/dist/web/assets/{dotenv-Dh3qf6yx.js → dotenv-NExMY2kS.js} +0 -0
  323. /package/dist/web/assets/{dracula-baAGmJYG.js → dracula-B9lbE01y.js} +0 -0
  324. /package/dist/web/assets/{dracula-soft-l7ZIyums.js → dracula-soft-CMBBfKF_.js} +0 -0
  325. /package/dist/web/assets/{dream-maker-DeRjdBn8.js → dream-maker-DEyDwFsd.js} +0 -0
  326. /package/dist/web/assets/{emacs-lisp-CB05-jQA.js → emacs-lisp-D_qP2zS6.js} +0 -0
  327. /package/dist/web/assets/{everforest-dark-CT-llQBT.js → everforest-dark-DbHa2U-p.js} +0 -0
  328. /package/dist/web/assets/{everforest-light-ByPJkTT7.js → everforest-light-BISkJchP.js} +0 -0
  329. /package/dist/web/assets/{fennel-CeMarbvn.js → fennel-6B22SfJR.js} +0 -0
  330. /package/dist/web/assets/{file-exclamation-point-COAwgNGw.js → file-exclamation-point-a8f3_w7a.js} +0 -0
  331. /package/dist/web/assets/{fish-BYrt6Xtb.js → fish-CubIIvJD.js} +0 -0
  332. /package/dist/web/assets/{fluent-Cr5VcAwh.js → fluent-DF55zQ09.js} +0 -0
  333. /package/dist/web/assets/{fortran-free-form-DQmKtrLa.js → fortran-free-form-DvOu8jAt.js} +0 -0
  334. /package/dist/web/assets/{gdscript-QTDKjzhY.js → gdscript-DCPxdLpq.js} +0 -0
  335. /package/dist/web/assets/{gdshader-Chr-5UYt.js → gdshader-CTZ381FO.js} +0 -0
  336. /package/dist/web/assets/{genie-kWHMWnt0.js → genie-C_QDtvgU.js} +0 -0
  337. /package/dist/web/assets/{gherkin-DtEBQFUo.js → gherkin-BcvK42tF.js} +0 -0
  338. /package/dist/web/assets/{github-dark-B5aiUta2.js → github-dark-C9afs_4w.js} +0 -0
  339. /package/dist/web/assets/{github-dark-default-BkxtsTqR.js → github-dark-default-CKGVrl3Z.js} +0 -0
  340. /package/dist/web/assets/{github-dark-dimmed-B9QjR_0K.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
  341. /package/dist/web/assets/{github-dark-high-contrast-D6bO-5c0.js → github-dark-high-contrast-DW78QukY.js} +0 -0
  342. /package/dist/web/assets/{github-light-Cg2ZW_CJ.js → github-light-BnP2dznS.js} +0 -0
  343. /package/dist/web/assets/{github-light-default-eD7qUVlQ.js → github-light-default-C2fPZzPG.js} +0 -0
  344. /package/dist/web/assets/{github-light-high-contrast-CFnlSVjd.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
  345. /package/dist/web/assets/{gleam-BpYoOqIT.js → gleam-BpeKwEIO.js} +0 -0
  346. /package/dist/web/assets/{gn-BxwtZPau.js → gn-nUb_Mit0.js} +0 -0
  347. /package/dist/web/assets/{gnuplot-CRyiZDfl.js → gnuplot-D7I0v20p.js} +0 -0
  348. /package/dist/web/assets/{go-Dw1Qx94W.js → go-D7Qqska7.js} +0 -0
  349. /package/dist/web/assets/{groovy-zq_SbWk-.js → groovy-DS-pne5-.js} +0 -0
  350. /package/dist/web/assets/{gruvbox-dark-hard-DbbNy8-Q.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
  351. /package/dist/web/assets/{gruvbox-dark-medium-BQZ7Kj6C.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
  352. /package/dist/web/assets/{gruvbox-dark-soft-vNavErXi.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
  353. /package/dist/web/assets/{gruvbox-light-hard-AOFbCd7i.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
  354. /package/dist/web/assets/{gruvbox-light-medium-DqtxND6H.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
  355. /package/dist/web/assets/{gruvbox-light-soft-Cg0basu6.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
  356. /package/dist/web/assets/{haskell-CcsmcOez.js → haskell-uQcywkDH.js} +0 -0
  357. /package/dist/web/assets/{haxe-BimYY9X4.js → haxe-c6bI4_GT.js} +0 -0
  358. /package/dist/web/assets/{hcl-CTrXCK40.js → hcl-CQXTiEXq.js} +0 -0
  359. /package/dist/web/assets/{hjson-CmqqU6Qg.js → hjson-ChETf2bz.js} +0 -0
  360. /package/dist/web/assets/{hlsl-CuJtX_uf.js → hlsl-BMC4JGoE.js} +0 -0
  361. /package/dist/web/assets/{horizon-BGeSjDYJ.js → horizon-BusFlhLW.js} +0 -0
  362. /package/dist/web/assets/{horizon-bright-GYMgd3R0.js → horizon-bright-2oD0Mz7W.js} +0 -0
  363. /package/dist/web/assets/{houston--JD_5r2q.js → houston-DdbbjqFg.js} +0 -0
  364. /package/dist/web/assets/{hy-C9ILOiqk.js → hy-D_SOuqw9.js} +0 -0
  365. /package/dist/web/assets/{image-gallery-BWFpZAAP.js → image-gallery-CrM5fHKy.js} +0 -0
  366. /package/dist/web/assets/{imba-B6F3yus5.js → imba-BxLOHO5I.js} +0 -0
  367. /package/dist/web/assets/{ini-BfkQswMZ.js → ini-ox-CFcg0.js} +0 -0
  368. /package/dist/web/assets/{java-DEH5avQ1.js → java-Bzo8yIal.js} +0 -0
  369. /package/dist/web/assets/{javascript-Dr4yHuV2.js → javascript-DHuz9VXn.js} +0 -0
  370. /package/dist/web/assets/{json-BuR35ewf.js → json-0_y80FD5.js} +0 -0
  371. /package/dist/web/assets/{json5-Ny9ZiJAC.js → json5-CEQ50pGM.js} +0 -0
  372. /package/dist/web/assets/{jsonc-zYVuoZkR.js → jsonc-CfrLEAKA.js} +0 -0
  373. /package/dist/web/assets/{jsonl-B9xthb_H.js → jsonl-fKbrANC2.js} +0 -0
  374. /package/dist/web/assets/{jsonnet-CCoLJbMP.js → jsonnet-DKbkPaQ2.js} +0 -0
  375. /package/dist/web/assets/{jssm-W0GDlQXi.js → jssm-Dxxx1Hqu.js} +0 -0
  376. /package/dist/web/assets/{jsx-BrLJpWC-.js → jsx-DlJL-Oj3.js} +0 -0
  377. /package/dist/web/assets/{kanagawa-dragon-CuVwt_MO.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
  378. /package/dist/web/assets/{kanagawa-lotus-B3W_617P.js → kanagawa-lotus-BHltayiE.js} +0 -0
  379. /package/dist/web/assets/{kanagawa-wave-CEx4MP5W.js → kanagawa-wave-D-KXPjw6.js} +0 -0
  380. /package/dist/web/assets/{kdl-DilRejnB.js → kdl-DvtoJdFo.js} +0 -0
  381. /package/dist/web/assets/{kotlin-Bq5JtKaa.js → kotlin-CTS4MLcx.js} +0 -0
  382. /package/dist/web/assets/{kusto-Bhp2KYog.js → kusto-HtSE_sKm.js} +0 -0
  383. /package/dist/web/assets/{laserwave-WA_7c5hu.js → laserwave-DaXZG9ge.js} +0 -0
  384. /package/dist/web/assets/{lean-AEGcXwS-.js → lean-BYtGUfWx.js} +0 -0
  385. /package/dist/web/assets/{less-NPqH-26u.js → less-BoxmU_Mk.js} +0 -0
  386. /package/dist/web/assets/{lib-BFetOULu.js → lib-BzxL1ARZ.js} +0 -0
  387. /package/dist/web/assets/{libesm-tR4XlhMj.js → libesm-ah-mr-sh.js} +0 -0
  388. /package/dist/web/assets/{light-plus-B4xW7cgY.js → light-plus-Bt5CyUuo.js} +0 -0
  389. /package/dist/web/assets/{llvm-CPyWNcDE.js → llvm-CSdk9q9V.js} +0 -0
  390. /package/dist/web/assets/{log-CeQmiQSz.js → log-B-kp_3CL.js} +0 -0
  391. /package/dist/web/assets/{logo-BO0VrjBm.js → logo-DdIsXFDO.js} +0 -0
  392. /package/dist/web/assets/{luau-B6GT9kA1.js → luau-DUBpLlzQ.js} +0 -0
  393. /package/dist/web/assets/{make-CwfmN7e_.js → make-B3JVjoW_.js} +0 -0
  394. /package/dist/web/assets/{markdown-Bbe4FlLp.js → markdown-Seon0vHs.js} +0 -0
  395. /package/dist/web/assets/{material-theme-iD9XbfsB.js → material-theme-Nq-ChX1P.js} +0 -0
  396. /package/dist/web/assets/{material-theme-darker-u7M3XGe7.js → material-theme-darker-CPV4ayXJ.js} +0 -0
  397. /package/dist/web/assets/{material-theme-lighter-BGdI48zn.js → material-theme-lighter-0kyjYqEd.js} +0 -0
  398. /package/dist/web/assets/{material-theme-ocean-_mk62F5q.js → material-theme-ocean-DGpAp5co.js} +0 -0
  399. /package/dist/web/assets/{material-theme-palenight-CxFbDyAu.js → material-theme-palenight-B0AUn0NG.js} +0 -0
  400. /package/dist/web/assets/{matlab-Z0RUE3V_.js → matlab-C2waytxs.js} +0 -0
  401. /package/dist/web/assets/{mdx-BZ5wPYzV.js → mdx-DAis6R6I.js} +0 -0
  402. /package/dist/web/assets/{mermaid-CY1_aP-h.js → mermaid-_rnT2YGD.js} +0 -0
  403. /package/dist/web/assets/{min-dark-DKWtxOre.js → min-dark-BSSjchUB.js} +0 -0
  404. /package/dist/web/assets/{min-light-e1iwHDfP.js → min-light-DomAFQsa.js} +0 -0
  405. /package/dist/web/assets/{mipsasm-BiDfQ4ir.js → mipsasm-bBgtvPyG.js} +0 -0
  406. /package/dist/web/assets/{mojo-DteFEHsj.js → mojo-Cp9BayW0.js} +0 -0
  407. /package/dist/web/assets/{monokai-BgPbE9xZ.js → monokai-CL_9gbmn.js} +0 -0
  408. /package/dist/web/assets/{moonbit-mEbRp-mA.js → moonbit-BS_fcr-P.js} +0 -0
  409. /package/dist/web/assets/{move-CogqZw7A.js → move-C79AH45U.js} +0 -0
  410. /package/dist/web/assets/{narrat-BD2qjmy1.js → narrat-CJ5s3BDf.js} +0 -0
  411. /package/dist/web/assets/{nextflow-groovy-BNX8GOD_.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
  412. /package/dist/web/assets/{night-owl-ed3GbziD.js → night-owl-Jkf9zFQK.js} +0 -0
  413. /package/dist/web/assets/{night-owl-light-B0j7O0Wn.js → night-owl-light-DRPeaG5J.js} +0 -0
  414. /package/dist/web/assets/{nix-DFDbcQQm.js → nix-D2Jet9DN.js} +0 -0
  415. /package/dist/web/assets/{nord-D93TxEQi.js → nord-CREVs22j.js} +0 -0
  416. /package/dist/web/assets/{nushell-3_H_XKja.js → nushell-CAiDi_mR.js} +0 -0
  417. /package/dist/web/assets/{objective-c-BM3u6nlm.js → objective-c-CVkmJkC1.js} +0 -0
  418. /package/dist/web/assets/{objective-cpp-DZ73h42g.js → objective-cpp-DIKDO1Ly.js} +0 -0
  419. /package/dist/web/assets/{ocaml-B1PU5gmh.js → ocaml-R9qtGQhs.js} +0 -0
  420. /package/dist/web/assets/{odin-D14L6aRL.js → odin-DTYNQqVr.js} +0 -0
  421. /package/dist/web/assets/{one-dark-pro-Bj-94Z8v.js → one-dark-pro-BSLWUJn5.js} +0 -0
  422. /package/dist/web/assets/{one-light-DTdGWyOZ.js → one-light-DbkRXG3t.js} +0 -0
  423. /package/dist/web/assets/{openscad-Ciq554Sj.js → openscad-BlPcSnmT.js} +0 -0
  424. /package/dist/web/assets/{pascal-Bfd4qdGi.js → pascal-B1Y46ZZB.js} +0 -0
  425. /package/dist/web/assets/{pkl-CTurDUfj.js → pkl-BhJSJbFv.js} +0 -0
  426. /package/dist/web/assets/{plastic-BLQfZXQp.js → plastic-BzxsCTpi.js} +0 -0
  427. /package/dist/web/assets/{plsql-DyYfP9kQ.js → plsql-DBba7dyP.js} +0 -0
  428. /package/dist/web/assets/{po-CgXdT8vQ.js → po-BvgLlnNo.js} +0 -0
  429. /package/dist/web/assets/{poimandres-DOg5fK6s.js → poimandres-DUaI_1JP.js} +0 -0
  430. /package/dist/web/assets/{polar-Dv4aoL6X.js → polar-C7uILwJn.js} +0 -0
  431. /package/dist/web/assets/{postcss-DxugHnJD.js → postcss-BgJlpFdw.js} +0 -0
  432. /package/dist/web/assets/{powerquery-CR3kzRyX.js → powerquery-Dn1MIpZu.js} +0 -0
  433. /package/dist/web/assets/{powershell-BE91xJQB.js → powershell-Bo-DYzOS.js} +0 -0
  434. /package/dist/web/assets/{prisma-B_3yUlRQ.js → prisma-CTLs5IdT.js} +0 -0
  435. /package/dist/web/assets/{prolog-DcT781Ht.js → prolog-QRGun3eG.js} +0 -0
  436. /package/dist/web/assets/{proto-CSf-w5k2.js → proto-B7PI7apn.js} +0 -0
  437. /package/dist/web/assets/{puppet-CLGO6QWl.js → puppet-CBq0tLrP.js} +0 -0
  438. /package/dist/web/assets/{purescript-Bah2McKd.js → purescript-DFewOQb2.js} +0 -0
  439. /package/dist/web/assets/{python-DUH8qy7N.js → python-iWuxKXvT.js} +0 -0
  440. /package/dist/web/assets/{qmldir-YIsHnjKq.js → qmldir-cXiW3AsF.js} +0 -0
  441. /package/dist/web/assets/{qss-CLmoVNA4.js → qss-DASwAbBL.js} +0 -0
  442. /package/dist/web/assets/{r-Pon-b-vz.js → r-TU2eaIPw.js} +0 -0
  443. /package/dist/web/assets/{racket-BA09P5Kg.js → racket-SkRKqrLY.js} +0 -0
  444. /package/dist/web/assets/{raku-DlpzU7Fe.js → raku-D31lthkZ.js} +0 -0
  445. /package/dist/web/assets/{react-w6wINGnd.js → react-I_EwPIZi.js} +0 -0
  446. /package/dist/web/assets/{red-l7VuCR6d.js → red-CTcQZevb.js} +0 -0
  447. /package/dist/web/assets/{reg-g_k7gQEr.js → reg-D8YAsTGf.js} +0 -0
  448. /package/dist/web/assets/{regexp-CT17B7I7.js → regexp-CD2uvAWx.js} +0 -0
  449. /package/dist/web/assets/{rel-DkPCS7Bp.js → rel-CAaI-1fg.js} +0 -0
  450. /package/dist/web/assets/{resolve-theme-DdB7Yo5Z.js → resolve-theme-CJvkL8hU.js} +0 -0
  451. /package/dist/web/assets/{riscv-CYuFTw30.js → riscv-DR-hJVIj.js} +0 -0
  452. /package/dist/web/assets/{ron-cY7u_yEu.js → ron-CuUCJ0n4.js} +0 -0
  453. /package/dist/web/assets/{rose-pine-Cr_h_Ubo.js → rose-pine-aFHxW48i.js} +0 -0
  454. /package/dist/web/assets/{rose-pine-dawn-rRewaSZg.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
  455. /package/dist/web/assets/{rose-pine-moon-3VX5Xwca.js → rose-pine-moon-wqyh0an4.js} +0 -0
  456. /package/dist/web/assets/{rosmsg-Du4ie5PU.js → rosmsg-CI1ywimO.js} +0 -0
  457. /package/dist/web/assets/{rust-B7xWL-Eh.js → rust-p7Imk4G_.js} +0 -0
  458. /package/dist/web/assets/{sass-oXbOG8de.js → sass-C8JfaqCV.js} +0 -0
  459. /package/dist/web/assets/{save-C4z1p51e.js → save-DRPrDfTt.js} +0 -0
  460. /package/dist/web/assets/{scala-nlZJhKHf.js → scala-DNxCW1By.js} +0 -0
  461. /package/dist/web/assets/{scheme-FZ9_Zy8D.js → scheme-BYuUPJoL.js} +0 -0
  462. /package/dist/web/assets/{sdbl-D_lyvXiu.js → sdbl-DbPeJPUr.js} +0 -0
  463. /package/dist/web/assets/{shellscript-DRWRboAd.js → shellscript-DMiHlR3q.js} +0 -0
  464. /package/dist/web/assets/{shield-check-C5laObJv.js → shield-check-8GObuUAn.js} +0 -0
  465. /package/dist/web/assets/{shield-off-DULFtyLS.js → shield-off-egKAZEwR.js} +0 -0
  466. /package/dist/web/assets/{slack-dark-BGk2uVHu.js → slack-dark-C4xGcQXI.js} +0 -0
  467. /package/dist/web/assets/{slack-ochin-PAoSX041.js → slack-ochin-BzgRt_wm.js} +0 -0
  468. /package/dist/web/assets/{smalltalk-qP0zvICo.js → smalltalk-LDeL9PYH.js} +0 -0
  469. /package/dist/web/assets/{snazzy-light-B7uccpH6.js → snazzy-light-CXE7a5j_.js} +0 -0
  470. /package/dist/web/assets/{solarized-dark-CbICLZs3.js → solarized-dark-DgUhY74x.js} +0 -0
  471. /package/dist/web/assets/{solarized-light-CprWhyjj.js → solarized-light-DD9C-H-9.js} +0 -0
  472. /package/dist/web/assets/{solidity-B7pcm2K6.js → solidity-CAbDB4D4.js} +0 -0
  473. /package/dist/web/assets/{splunk-Co2LL4J_.js → splunk-kQREPfIc.js} +0 -0
  474. /package/dist/web/assets/{sql-cETIdGnT.js → sql-CBzeNAD8.js} +0 -0
  475. /package/dist/web/assets/{ssh-config-DETG6JCD.js → ssh-config-L1euxrsM.js} +0 -0
  476. /package/dist/web/assets/{stylus-tmTre2-O.js → stylus-DvBnYniP.js} +0 -0
  477. /package/dist/web/assets/{swift-BeKQbULV.js → swift-DF5NDQp6.js} +0 -0
  478. /package/dist/web/assets/{synthwave-84-eCL4-Xdn.js → synthwave-84-DbBhB_Hq.js} +0 -0
  479. /package/dist/web/assets/{system-verilog-CviwWvEx.js → system-verilog-D8s2ZW_k.js} +0 -0
  480. /package/dist/web/assets/{systemd-WuoFIbBS.js → systemd-a5-XZJsG.js} +0 -0
  481. /package/dist/web/assets/{table-CHmxuh6M.js → table-BX2ZrJfM.js} +0 -0
  482. /package/dist/web/assets/{talonscript-B9VBu7VI.js → talonscript-BLa0URTW.js} +0 -0
  483. /package/dist/web/assets/{tasl-CN7r11ky.js → tasl-DIoV4XcC.js} +0 -0
  484. /package/dist/web/assets/{tcl-e1cdhe3h.js → tcl-ztYAdXfQ.js} +0 -0
  485. /package/dist/web/assets/{terraform-BEP0mfa_.js → terraform-DvpCCiOZ.js} +0 -0
  486. /package/dist/web/assets/{tokyo-night-BfA_dA37.js → tokyo-night-BkHlYUv0.js} +0 -0
  487. /package/dist/web/assets/{toml-BrDIfNnt.js → toml-acIrxos6.js} +0 -0
  488. /package/dist/web/assets/{tsv-CyfGyE4i.js → tsv-BJB5EVCT.js} +0 -0
  489. /package/dist/web/assets/{tsx-BrFA8Iwf.js → tsx-B1MmXGT3.js} +0 -0
  490. /package/dist/web/assets/{turtle-9GBtR0mP.js → turtle-DFceTiqd.js} +0 -0
  491. /package/dist/web/assets/{typescript-BuTRZ65V.js → typescript-Dz9wU5Y1.js} +0 -0
  492. /package/dist/web/assets/{typespec-CRiNNRXF.js → typespec-CPuYZijj.js} +0 -0
  493. /package/dist/web/assets/{typst-CmpTnnkl.js → typst-CfsCIYv8.js} +0 -0
  494. /package/dist/web/assets/{utils-B9H0d3Fh.js → utils-DoY4yTP-.js} +0 -0
  495. /package/dist/web/assets/{v-DnIi2aab.js → v-CGo0jcXA.js} +0 -0
  496. /package/dist/web/assets/{vala-B7VxCNZP.js → vala-AsSR0hrb.js} +0 -0
  497. /package/dist/web/assets/{vb-DGoQ3pBT.js → vb-C7tvbXv1.js} +0 -0
  498. /package/dist/web/assets/{vendor-xterm-ClRVIJ-k.js → vendor-xterm-BeaIjRrv.js} +0 -0
  499. /package/dist/web/assets/{verilog-zGOC4qwV.js → verilog--a33ND-p.js} +0 -0
  500. /package/dist/web/assets/{vesper-DXR9-06q.js → vesper-B_Z0h4pn.js} +0 -0
  501. /package/dist/web/assets/{vhdl-CCO0oz0l.js → vhdl-CWL5dZPN.js} +0 -0
  502. /package/dist/web/assets/{viml-Dxwr-QNN.js → viml-BglmT14Y.js} +0 -0
  503. /package/dist/web/assets/{vitesse-black-Bhah7RxE.js → vitesse-black-RKFsSqQI.js} +0 -0
  504. /package/dist/web/assets/{vitesse-dark-CGK6XVwc.js → vitesse-dark-D7zDWNmc.js} +0 -0
  505. /package/dist/web/assets/{vitesse-light-ne5kfw8h.js → vitesse-light-MAp87W5b.js} +0 -0
  506. /package/dist/web/assets/{vyper-ErDFqVoP.js → vyper-CgCBYg1G.js} +0 -0
  507. /package/dist/web/assets/{wasm-DtWvUWGr.js → wasm-BOG7ZeTT.js} +0 -0
  508. /package/dist/web/assets/{wasm-Bd258nS1.js → wasm-Bwl4txwp.js} +0 -0
  509. /package/dist/web/assets/{wenyan-EZe1IEYT.js → wenyan-DpShPKXZ.js} +0 -0
  510. /package/dist/web/assets/{wgsl-B1o5_I_9.js → wgsl-BakcCblK.js} +0 -0
  511. /package/dist/web/assets/{wikitext-C_rwp8An.js → wikitext-NroRWVAO.js} +0 -0
  512. /package/dist/web/assets/{wit-gbYtF3rY.js → wit-B5CM2pg6.js} +0 -0
  513. /package/dist/web/assets/{wolfram-DrcQFe2n.js → wolfram-7MTFBJpR.js} +0 -0
  514. /package/dist/web/assets/{x-B1qHmLdR.js → x-BcqfTY_7.js} +0 -0
  515. /package/dist/web/assets/{yaml-CzDAObLX.js → yaml-Bwr01G2K.js} +0 -0
  516. /package/dist/web/assets/{zenscript-B-J1phtg.js → zenscript-Dj8GATvi.js} +0 -0
  517. /package/dist/web/assets/{zig-BlzNia0O.js → zig-LAE-fbS-.js} +0 -0
@@ -0,0 +1,103 @@
1
+ import { Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
2
+ import { Button } from "@/components/ui/button";
3
+ import { VideoSettingsMenu } from "./video-settings-menu";
4
+ import type { VideoTransform } from "./video-transform";
5
+
6
+ interface Props {
7
+ playing: boolean;
8
+ position: number;
9
+ duration: number | null;
10
+ volume: number;
11
+ muted: boolean;
12
+ speed: number;
13
+ transform: VideoTransform;
14
+ fullscreen: boolean;
15
+ onTogglePlay: () => void;
16
+ /** Live value while the thumb is dragged (updates the clock only). */
17
+ onScrub: (t: number) => void;
18
+ /** Final value when the drag ends. */
19
+ onSeek: (t: number) => void;
20
+ onVolume: (v: number) => void;
21
+ onToggleMute: () => void;
22
+ onSpeed: (rate: number) => void;
23
+ onRotate: (direction: 1 | -1) => void;
24
+ onFlipH: () => void;
25
+ onFlipV: () => void;
26
+ onResetTransform: () => void;
27
+ onToggleFullscreen: () => void;
28
+ }
29
+
30
+ /** Bottom bar: 44px targets, primary actions in thumb reach on phones. */
31
+ export function VideoPlayerControls(p: Props) {
32
+ const VolumeIcon = p.muted || p.volume === 0 ? VolumeX : p.volume < 0.5 ? Volume1 : Volume2;
33
+ const commit = (e: React.SyntheticEvent<HTMLInputElement>) => p.onSeek(Number((e.target as HTMLInputElement).value));
34
+
35
+ return (
36
+ <div className="flex items-center gap-1 px-2 py-1 bg-surface border-t border-border">
37
+ <Button variant="ghost" size="icon" className="size-11 shrink-0" onClick={p.onTogglePlay} aria-label={p.playing ? "Pause" : "Play"}>
38
+ {p.playing ? <Pause className="size-5" /> : <Play className="size-5" />}
39
+ </Button>
40
+
41
+ <span className="text-xs tabular-nums text-text-secondary shrink-0 min-w-[4.5rem] text-center">
42
+ {formatClock(p.position)}{p.duration ? ` / ${formatClock(p.duration)}` : ""}
43
+ </span>
44
+
45
+ {p.duration ? (
46
+ <input
47
+ type="range"
48
+ min={0}
49
+ max={p.duration}
50
+ step={0.1}
51
+ value={Math.min(p.position, p.duration)}
52
+ aria-label="Seek"
53
+ className="flex-1 h-11 accent-primary cursor-pointer min-w-0"
54
+ onChange={(e) => p.onScrub(Number(e.target.value))}
55
+ onPointerUp={commit}
56
+ onKeyUp={commit}
57
+ onTouchEnd={commit}
58
+ />
59
+ ) : <div className="flex-1" />}
60
+
61
+ {/* Volume: icon toggles mute, slider sets level. Slider hidden on phones (hardware keys). */}
62
+ <div className="flex items-center shrink-0">
63
+ <Button variant="ghost" size="icon" className="size-11" onClick={p.onToggleMute} aria-label={p.muted ? "Unmute" : "Mute"}>
64
+ <VolumeIcon className="size-5" />
65
+ </Button>
66
+ <input
67
+ type="range"
68
+ min={0}
69
+ max={1}
70
+ step={0.05}
71
+ value={p.muted ? 0 : p.volume}
72
+ aria-label="Volume"
73
+ className="hidden sm:block w-20 h-11 accent-primary cursor-pointer"
74
+ onChange={(e) => p.onVolume(Number(e.target.value))}
75
+ />
76
+ </div>
77
+
78
+ <VideoSettingsMenu
79
+ speed={p.speed}
80
+ onSpeed={p.onSpeed}
81
+ transform={p.transform}
82
+ onRotate={p.onRotate}
83
+ onFlipH={p.onFlipH}
84
+ onFlipV={p.onFlipV}
85
+ onResetTransform={p.onResetTransform}
86
+ />
87
+
88
+ <Button variant="ghost" size="icon" className="size-11 shrink-0" onClick={p.onToggleFullscreen} aria-label={p.fullscreen ? "Exit fullscreen" : "Fullscreen"}>
89
+ {p.fullscreen ? <Minimize className="size-5" /> : <Maximize className="size-5" />}
90
+ </Button>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ /** 65 → "1:05", 3725 → "1:02:05". */
96
+ export function formatClock(sec: number): string {
97
+ const s = Math.max(0, Math.floor(Number.isFinite(sec) ? sec : 0));
98
+ const h = Math.floor(s / 3600);
99
+ const m = Math.floor((s % 3600) / 60);
100
+ const r = s % 60;
101
+ const mm = h ? String(m).padStart(2, "0") : String(m);
102
+ return `${h ? `${h}:` : ""}${mm}:${String(r).padStart(2, "0")}`;
103
+ }
@@ -0,0 +1,190 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Loader2 } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { rawMediaUrl, transcodeMediaUrl } from "@/lib/media-url";
5
+ import { VideoPlayerControls } from "./video-player-controls";
6
+ import { SPEED_STEPS } from "./video-settings-menu";
7
+ import { useVideoKeyboardShortcuts, type VideoPlayerActions } from "./use-video-keyboard-shortcuts";
8
+ import {
9
+ IDENTITY_TRANSFORM, fittedMaxSize, rotateClockwise, rotateCounterClockwise, toCssTransform, type VideoTransform,
10
+ } from "./video-transform";
11
+
12
+ interface Props {
13
+ filePath: string;
14
+ projectName: string;
15
+ /**
16
+ * `native`: the browser decodes the file itself, streamed by Range requests — seeking
17
+ * is a `currentTime` assignment. `transcode`: ffmpeg pipes a live fragmented MP4 with
18
+ * no duration, so the bar uses ffprobe's duration and a seek restarts the stream at
19
+ * `?start=`; position on screen = start + currentTime.
20
+ */
21
+ mode: "native" | "transcode";
22
+ /** Seconds from ffprobe (transcode mode); native mode reads it from the element. */
23
+ probeDuration?: number | null;
24
+ /** Native decode failed (e.g. HEVC in .mov) — caller may retry through ffmpeg. */
25
+ onNativeError?: () => void;
26
+ }
27
+
28
+ /** Custom player shared by both playback paths so rotation, speed and shortcuts behave the same. */
29
+ export function VideoPlayer({ filePath, projectName, mode, probeDuration = null, onNativeError }: Props) {
30
+ const rootRef = useRef<HTMLDivElement>(null);
31
+ const stageRef = useRef<HTMLDivElement>(null);
32
+ const videoRef = useRef<HTMLVideoElement>(null);
33
+ const resumeRef = useRef(true);
34
+
35
+ const [start, setStart] = useState(0);
36
+ const [elapsed, setElapsed] = useState(0);
37
+ const [nativeDuration, setNativeDuration] = useState<number | null>(null);
38
+ const [playing, setPlaying] = useState(false);
39
+ const [waiting, setWaiting] = useState(true);
40
+ const [scrub, setScrub] = useState<number | null>(null);
41
+ const [volume, setVolume] = useState(1);
42
+ const [muted, setMuted] = useState(false);
43
+ const [speed, setSpeed] = useState(1);
44
+ const [transform, setTransform] = useState<VideoTransform>(IDENTITY_TRANSFORM);
45
+ const [fullscreen, setFullscreen] = useState(false);
46
+ const [box, setBox] = useState({ width: 0, height: 0 });
47
+ const [error, setError] = useState<string | null>(null);
48
+
49
+ const isTranscode = mode === "transcode";
50
+ const src = isTranscode ? transcodeMediaUrl(filePath, projectName, start) : rawMediaUrl(filePath, projectName);
51
+ const duration = isTranscode ? probeDuration : nativeDuration;
52
+ const position = scrub ?? (isTranscode ? start + elapsed : elapsed);
53
+
54
+ // New source (mount, other file, or transcode restart) → back to loading state.
55
+ useEffect(() => { setElapsed(0); setWaiting(true); setError(null); }, [src]);
56
+ useEffect(() => { setStart(0); setNativeDuration(null); setTransform(IDENTITY_TRANSFORM); }, [filePath, mode]);
57
+
58
+ // Element properties survive src changes but not a remount; keep them in sync with state.
59
+ useEffect(() => { const v = videoRef.current; if (v) { v.volume = volume; v.muted = muted; } }, [volume, muted]);
60
+ useEffect(() => { const v = videoRef.current; if (v) v.playbackRate = speed; }, [speed, src]);
61
+
62
+ // A detached <video> keeps its network fetch alive until garbage-collected, which
63
+ // would leave ffmpeg encoding for nobody. Emptying the source aborts the load now.
64
+ // `isConnected` guards StrictMode's simulated unmount, where the element stays in the
65
+ // DOM and React would not re-apply the src attribute afterwards.
66
+ useEffect(() => {
67
+ const v = videoRef.current;
68
+ return () => { if (v && !v.isConnected) { v.pause(); v.removeAttribute("src"); v.load(); } };
69
+ }, []);
70
+
71
+ useEffect(() => {
72
+ const onFs = () => setFullscreen(document.fullscreenElement === rootRef.current);
73
+ document.addEventListener("fullscreenchange", onFs);
74
+ return () => document.removeEventListener("fullscreenchange", onFs);
75
+ }, []);
76
+
77
+ // Rotated 90°/270° video must be sized against the swapped stage axes.
78
+ useEffect(() => {
79
+ const el = stageRef.current;
80
+ if (!el) return;
81
+ const ro = new ResizeObserver(() => setBox({ width: el.clientWidth, height: el.clientHeight }));
82
+ ro.observe(el);
83
+ return () => ro.disconnect();
84
+ }, []);
85
+
86
+ const seekTo = useCallback((t: number) => {
87
+ const v = videoRef.current;
88
+ const clamped = Math.max(0, duration ? Math.min(t, duration - 0.5) : t);
89
+ setScrub(null);
90
+ if (!isTranscode) { if (v) v.currentTime = clamped; return; }
91
+ // Restarting ffmpeg for a sub-second nudge is wasteful.
92
+ if (Math.abs(clamped - (start + (v?.currentTime ?? 0))) < 0.5) return;
93
+ resumeRef.current = !(v?.paused ?? false);
94
+ setStart(clamped);
95
+ }, [duration, isTranscode, start]);
96
+
97
+ const actions = useMemo<VideoPlayerActions>(() => ({
98
+ togglePlay: () => { const v = videoRef.current; if (!v) return; if (v.paused) void v.play().catch(() => {}); else v.pause(); },
99
+ seekBy: (d) => seekTo((isTranscode ? start : 0) + (videoRef.current?.currentTime ?? 0) + d),
100
+ toggleFullscreen: () => {
101
+ if (document.fullscreenElement) void document.exitFullscreen();
102
+ else void rootRef.current?.requestFullscreen?.();
103
+ },
104
+ toggleMute: () => setMuted((m) => !m),
105
+ volumeBy: (d) => { setMuted(false); setVolume((v) => Math.min(1, Math.max(0, Math.round((v + d) * 100) / 100))); },
106
+ speedStep: (dir) => setSpeed((s) => {
107
+ const i = SPEED_STEPS.indexOf(s as typeof SPEED_STEPS[number]);
108
+ return SPEED_STEPS[Math.min(SPEED_STEPS.length - 1, Math.max(0, (i === -1 ? 3 : i) + dir))] ?? 1;
109
+ }),
110
+ rotate: (dir) => setTransform((t) => (dir === 1 ? rotateClockwise(t) : rotateCounterClockwise(t))),
111
+ }), [seekTo, isTranscode, start]);
112
+
113
+ const onKeyDown = useVideoKeyboardShortcuts(actions);
114
+ const fitted = fittedMaxSize(transform, box);
115
+
116
+ if (error) {
117
+ return (
118
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary p-4 text-center">
119
+ <p className="text-sm">{error}</p>
120
+ <Button variant="outline" size="sm" onClick={() => { setError(null); setStart((s) => s + 0.001); }}>Retry</Button>
121
+ </div>
122
+ );
123
+ }
124
+
125
+ return (
126
+ <div
127
+ ref={rootRef}
128
+ data-testid="video-player"
129
+ tabIndex={0}
130
+ onKeyDown={onKeyDown}
131
+ className="flex flex-col h-full bg-black outline-none focus-visible:ring-1 focus-visible:ring-primary/60"
132
+ >
133
+ <div
134
+ ref={stageRef}
135
+ className="relative flex-1 min-h-0 flex items-center justify-center overflow-hidden"
136
+ onClick={(e) => { rootRef.current?.focus(); if (e.detail === 1) actions.togglePlay(); }}
137
+ onDoubleClick={actions.toggleFullscreen}
138
+ >
139
+ <video
140
+ ref={videoRef}
141
+ src={src}
142
+ autoPlay
143
+ playsInline
144
+ preload={isTranscode ? "auto" : "metadata"}
145
+ style={{ transform: toCssTransform(transform), maxWidth: fitted.maxWidth || undefined, maxHeight: fitted.maxHeight || undefined }}
146
+ onLoadedMetadata={(e) => {
147
+ const v = e.currentTarget;
148
+ v.playbackRate = speed;
149
+ if (!isTranscode && Number.isFinite(v.duration)) setNativeDuration(v.duration);
150
+ if (isTranscode && !resumeRef.current) v.pause();
151
+ }}
152
+ onPlay={() => setPlaying(true)}
153
+ onPause={() => setPlaying(false)}
154
+ onWaiting={() => setWaiting(true)}
155
+ onPlaying={() => setWaiting(false)}
156
+ onCanPlay={() => setWaiting(false)}
157
+ onTimeUpdate={(e) => setElapsed(e.currentTarget.currentTime)}
158
+ onEnded={() => setPlaying(false)}
159
+ onError={() => {
160
+ if (!isTranscode) onNativeError?.();
161
+ else setError("Transcoding failed. ffmpeg may have stopped or the file is corrupt.");
162
+ }}
163
+ />
164
+ {waiting && <Loader2 className="absolute size-8 animate-spin text-white/80 pointer-events-none" />}
165
+ </div>
166
+
167
+ <VideoPlayerControls
168
+ playing={playing}
169
+ position={position}
170
+ duration={duration}
171
+ volume={volume}
172
+ muted={muted}
173
+ speed={speed}
174
+ transform={transform}
175
+ fullscreen={fullscreen}
176
+ onTogglePlay={actions.togglePlay}
177
+ onScrub={setScrub}
178
+ onSeek={seekTo}
179
+ onVolume={(v) => { setVolume(v); setMuted(v === 0); }}
180
+ onToggleMute={actions.toggleMute}
181
+ onSpeed={setSpeed}
182
+ onRotate={actions.rotate}
183
+ onFlipH={() => setTransform((t) => ({ ...t, flipH: !t.flipH }))}
184
+ onFlipV={() => setTransform((t) => ({ ...t, flipV: !t.flipV }))}
185
+ onResetTransform={() => setTransform(IDENTITY_TRANSFORM)}
186
+ onToggleFullscreen={actions.toggleFullscreen}
187
+ />
188
+ </div>
189
+ );
190
+ }
@@ -0,0 +1,92 @@
1
+ import { FlipHorizontal2, FlipVertical2, RotateCcw, RotateCw, Settings, Undo2 } from "lucide-react";
2
+ import { Button } from "@/components/ui/button";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuLabel,
8
+ DropdownMenuRadioGroup,
9
+ DropdownMenuRadioItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from "@/components/ui/dropdown-menu";
13
+ import { isIdentity, type VideoTransform } from "./video-transform";
14
+ import { VIDEO_SHORTCUTS } from "./use-video-keyboard-shortcuts";
15
+
16
+ export const SPEED_STEPS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3] as const;
17
+
18
+ interface Props {
19
+ speed: number;
20
+ onSpeed: (rate: number) => void;
21
+ transform: VideoTransform;
22
+ onRotate: (direction: 1 | -1) => void;
23
+ onFlipH: () => void;
24
+ onFlipV: () => void;
25
+ onResetTransform: () => void;
26
+ }
27
+
28
+ /** Gear menu: playback speed, orientation fixes, and the shortcut cheat-sheet. */
29
+ export function VideoSettingsMenu({ speed, onSpeed, transform, onRotate, onFlipH, onFlipV, onResetTransform }: Props) {
30
+ return (
31
+ <DropdownMenu>
32
+ <DropdownMenuTrigger asChild>
33
+ <Button variant="ghost" size="icon" className="size-11 shrink-0 relative" aria-label="Playback settings">
34
+ <Settings className="size-5" />
35
+ {speed !== 1 && (
36
+ <span className="absolute -top-0.5 -right-0.5 text-[10px] leading-none px-1 rounded bg-primary text-primary-foreground tabular-nums">
37
+ {speed}x
38
+ </span>
39
+ )}
40
+ </Button>
41
+ </DropdownMenuTrigger>
42
+ {/* Flat list on purpose: sub-menus are awkward on touch screens. */}
43
+ <DropdownMenuContent align="end" className="w-60 max-h-[70vh] overflow-y-auto">
44
+ <DropdownMenuLabel>Speed</DropdownMenuLabel>
45
+ <DropdownMenuRadioGroup value={String(speed)} onValueChange={(v) => onSpeed(Number(v))}>
46
+ <div className="grid grid-cols-4 gap-1 px-1 pb-1">
47
+ {SPEED_STEPS.map((s) => (
48
+ <DropdownMenuRadioItem
49
+ key={s}
50
+ value={String(s)}
51
+ className="justify-center pl-2 min-h-11 text-xs tabular-nums data-[state=checked]:bg-accent [&>span:first-child]:hidden"
52
+ >
53
+ {s}x
54
+ </DropdownMenuRadioItem>
55
+ ))}
56
+ </div>
57
+ </DropdownMenuRadioGroup>
58
+
59
+ <DropdownMenuSeparator />
60
+ <DropdownMenuLabel>Orientation</DropdownMenuLabel>
61
+ <DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onRotate(1); }}>
62
+ <RotateCw className="size-4" /> Rotate right <span className="ml-auto text-xs text-text-subtle">R</span>
63
+ </DropdownMenuItem>
64
+ <DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onRotate(-1); }}>
65
+ <RotateCcw className="size-4" /> Rotate left <span className="ml-auto text-xs text-text-subtle">⇧R</span>
66
+ </DropdownMenuItem>
67
+ <DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onFlipH(); }}>
68
+ <FlipHorizontal2 className="size-4" /> Flip horizontal {transform.flipH && <span className="ml-auto text-xs">✓</span>}
69
+ </DropdownMenuItem>
70
+ <DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onFlipV(); }}>
71
+ <FlipVertical2 className="size-4" /> Flip vertical {transform.flipV && <span className="ml-auto text-xs">✓</span>}
72
+ </DropdownMenuItem>
73
+ {!isIdentity(transform) && (
74
+ <DropdownMenuItem className="min-h-11" onSelect={onResetTransform}>
75
+ <Undo2 className="size-4" /> Reset orientation
76
+ </DropdownMenuItem>
77
+ )}
78
+
79
+ <DropdownMenuSeparator />
80
+ <DropdownMenuLabel>Keyboard</DropdownMenuLabel>
81
+ <div className="px-2 pb-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs text-text-secondary">
82
+ {VIDEO_SHORTCUTS.map(([keys, action]) => (
83
+ <div key={keys} className="contents">
84
+ <kbd className="font-mono text-text-subtle whitespace-nowrap">{keys}</kbd>
85
+ <span>{action}</span>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ </DropdownMenuContent>
90
+ </DropdownMenu>
91
+ );
92
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Rotation/flip state for the video player, applied as a CSS transform.
3
+ *
4
+ * Camera files are often recorded sideways (dashcams, phones) and carry no rotation
5
+ * metadata, so the viewer needs a manual fix. Kept as pure functions so the layout
6
+ * math is testable without a DOM.
7
+ */
8
+
9
+ export interface VideoTransform {
10
+ /** Clockwise degrees, one of 0 / 90 / 180 / 270. */
11
+ rotation: 0 | 90 | 180 | 270;
12
+ flipH: boolean;
13
+ flipV: boolean;
14
+ }
15
+
16
+ export const IDENTITY_TRANSFORM: VideoTransform = { rotation: 0, flipH: false, flipV: false };
17
+
18
+ export function rotateClockwise(t: VideoTransform): VideoTransform {
19
+ return { ...t, rotation: ((t.rotation + 90) % 360) as VideoTransform["rotation"] };
20
+ }
21
+
22
+ export function rotateCounterClockwise(t: VideoTransform): VideoTransform {
23
+ return { ...t, rotation: ((t.rotation + 270) % 360) as VideoTransform["rotation"] };
24
+ }
25
+
26
+ export function isIdentity(t: VideoTransform): boolean {
27
+ return t.rotation === 0 && !t.flipH && !t.flipV;
28
+ }
29
+
30
+ /** CSS `transform` value. Flip is applied in the video's own axes, before rotation. */
31
+ export function toCssTransform(t: VideoTransform): string {
32
+ const parts: string[] = [];
33
+ if (t.rotation) parts.push(`rotate(${t.rotation}deg)`);
34
+ if (t.flipH) parts.push("scaleX(-1)");
35
+ if (t.flipV) parts.push("scaleY(-1)");
36
+ return parts.length ? parts.join(" ") : "none";
37
+ }
38
+
39
+ /**
40
+ * Size the element must be laid out at so that, once rotated, it fits `box`.
41
+ * A 90°/270° rotation swaps the visual axes, so the element's width is limited by
42
+ * the box's height and vice versa. Returns max-width/max-height in CSS pixels.
43
+ */
44
+ export function fittedMaxSize(t: VideoTransform, box: { width: number; height: number }): { maxWidth: number; maxHeight: number } {
45
+ const swapped = t.rotation === 90 || t.rotation === 270;
46
+ return swapped
47
+ ? { maxWidth: box.height, maxHeight: box.width }
48
+ : { maxWidth: box.width, maxHeight: box.height };
49
+ }
@@ -1,23 +1,84 @@
1
- import { Loader2, FileWarning } from "lucide-react";
2
- import { useBlobUrl } from "@/hooks/use-blob-url";
1
+ import { useEffect, useState } from "react";
2
+ import { Download, FileWarning, Loader2 } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { extensionOf } from "@/components/os-explorer/can-open-in-ppm";
5
+ import { probeMedia, rawMediaUrl, type MediaProbeInfo } from "@/lib/media-url";
6
+ import { VideoPlayer } from "./video-player/video-player";
3
7
 
8
+ /** Containers every browser demuxes natively; anything else goes through ffmpeg. */
9
+ const NATIVE_CONTAINERS = new Set(["mp4", "m4v", "webm", "ogg", "ogv", "mov"]);
10
+
11
+ type Mode =
12
+ | { kind: "native" }
13
+ | { kind: "probing" }
14
+ | { kind: "transcode"; probe: MediaProbeInfo }
15
+ | { kind: "unsupported"; probe: MediaProbeInfo | null };
16
+
17
+ /**
18
+ * Streams the file straight from its URL so the browser fetches it in byte ranges
19
+ * (instant start, cheap seeking, no whole-file blob in memory). Files the browser
20
+ * cannot decode — AVI, MKV, or an mp4 with HEVC inside — fall back to server-side
21
+ * transcoding when ffmpeg is installed.
22
+ */
4
23
  export function VideoPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
5
- const { blobUrl, error } = useBlobUrl(filePath, projectName);
24
+ const ext = extensionOf(filePath);
25
+ const [mode, setMode] = useState<Mode>(() => (NATIVE_CONTAINERS.has(ext) ? { kind: "native" } : { kind: "probing" }));
6
26
 
7
- if (error) {
8
- return (
9
- <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
10
- <FileWarning className="size-10 text-text-subtle" />
11
- <p className="text-sm">Failed to load video.</p>
12
- </div>
13
- );
14
- }
15
- if (!blobUrl) {
27
+ // Reset when a different file is shown in the same tab.
28
+ useEffect(() => {
29
+ setMode(NATIVE_CONTAINERS.has(extensionOf(filePath)) ? { kind: "native" } : { kind: "probing" });
30
+ }, [filePath]);
31
+
32
+ useEffect(() => {
33
+ if (mode.kind !== "probing") return;
34
+ let cancelled = false;
35
+ probeMedia(filePath, projectName)
36
+ .then((probe) => {
37
+ if (cancelled) return;
38
+ setMode(probe.transcodable ? { kind: "transcode", probe } : { kind: "unsupported", probe });
39
+ })
40
+ .catch(() => { if (!cancelled) setMode({ kind: "unsupported", probe: null }); });
41
+ return () => { cancelled = true; };
42
+ }, [mode.kind, filePath, projectName]);
43
+
44
+ if (mode.kind === "probing") {
16
45
  return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
17
46
  }
47
+ if (mode.kind === "transcode") {
48
+ return <VideoPlayer filePath={filePath} projectName={projectName} mode="transcode" probeDuration={mode.probe.duration} />;
49
+ }
50
+ if (mode.kind === "unsupported") {
51
+ return <UnsupportedVideo filePath={filePath} projectName={projectName} probe={mode.probe} />;
52
+ }
53
+ return (
54
+ <VideoPlayer
55
+ filePath={filePath}
56
+ projectName={projectName}
57
+ mode="native"
58
+ // A native container can still hide a codec the browser lacks (HEVC in .mov);
59
+ // let ffmpeg have a go before giving up.
60
+ onNativeError={() => setMode({ kind: "probing" })}
61
+ />
62
+ );
63
+ }
64
+
65
+ function UnsupportedVideo({ filePath, projectName, probe }: { filePath: string; projectName: string; probe: MediaProbeInfo | null }) {
66
+ const codecs = [probe?.video?.codec, probe?.audio?.codec].filter(Boolean).join(" + ");
18
67
  return (
19
- <div className="flex items-center justify-center h-full p-4 bg-surface overflow-auto">
20
- <video src={blobUrl} controls className="max-w-full max-h-full" />
68
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary p-6 text-center">
69
+ <FileWarning className="size-10 text-text-subtle" />
70
+ <p className="text-sm">
71
+ This video cannot be played in the browser{codecs ? ` (${codecs})` : ""}.
72
+ </p>
73
+ <p className="text-xs text-text-subtle max-w-sm">
74
+ Install <code>ffmpeg</code> on the PPM server (e.g. <code>winget install ffmpeg</code>, <code>brew install ffmpeg</code>,{" "}
75
+ <code>apt install ffmpeg</code>) and restart PPM to transcode it on the fly.
76
+ </p>
77
+ <Button asChild variant="outline" size="sm" className="h-11 px-4">
78
+ <a href={`${rawMediaUrl(filePath, projectName)}&download=true`} download>
79
+ <Download className="size-4 mr-2" /> Download file
80
+ </a>
81
+ </Button>
21
82
  </div>
22
83
  );
23
84
  }
@@ -41,6 +41,7 @@ import { flattenVisibleTree, type InputRow } from "./flatten-visible-tree";
41
41
  import { downloadFile, downloadFolder } from "@/lib/file-download";
42
42
  import { api, projectUrl } from "@/lib/api-client";
43
43
  import { openExplorer } from "@/components/os-explorer/open-explorer";
44
+ import { openTerminalAt } from "@/lib/open-terminal-at";
44
45
  import { fsChanged, onFsChanged } from "@/components/os-explorer/explorer-store";
45
46
  import { dirnameOf } from "@/components/os-explorer/format-file-meta";
46
47
  import { useDragAutoScroll } from "@/components/os-explorer/dnd/use-drag-auto-scroll";
@@ -378,6 +379,11 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
378
379
  void openExplorer(absoluteProjectPath(activeProject!.path, target));
379
380
  return;
380
381
  }
382
+ if (action === "open-in-terminal") {
383
+ const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
384
+ openTerminalAt(absoluteProjectPath(activeProject!.path, target), activeProject!.name);
385
+ return;
386
+ }
381
387
  if (action === "paste" && node.type === "directory") {
382
388
  pasteFiles(node.path);
383
389
  return;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Context menu items for a tree node (file or folder).
3
3
  */
4
- import { Download, FolderOpen } from "lucide-react";
4
+ import { Download, FolderOpen, TerminalSquare } from "lucide-react";
5
5
  import type { FileNode, ClipboardState } from "@/stores/file-store";
6
6
  import {
7
7
  ContextMenuContent,
@@ -43,6 +43,10 @@ export function TreeNodeContextMenu({
43
43
  <FolderOpen className="size-3.5 mr-2" />
44
44
  Open in File Explorer
45
45
  </ContextMenuItem>
46
+ <ContextMenuItem onClick={() => onAction("open-in-terminal", node)}>
47
+ <TerminalSquare className="size-3.5 mr-2" />
48
+ Open in Terminal
49
+ </ContextMenuItem>
46
50
  <ContextMenuSeparator />
47
51
  </>
48
52
  )}
@@ -21,6 +21,7 @@ export interface WindowContentProps {
21
21
 
22
22
  export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentType<WindowContentProps>>> = {
23
23
  explorer: lazy(() => import("@/components/os-explorer/explorer-window-content")),
24
+ "team-member": lazy(() => import("@/components/chat/team-member-window-content")),
24
25
  };
25
26
 
26
27
  /**
@@ -41,6 +42,10 @@ export function chromeFor(kind: WindowKind): WindowChrome | undefined {
41
42
  export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>): string {
42
43
  const explicit = payload?.title;
43
44
  if (typeof explicit === "string" && explicit.trim()) return explicit;
45
+ if (kind === "team-member") {
46
+ const member = payload?.memberName;
47
+ return typeof member === "string" && member ? `Session — ${member}` : "Team member";
48
+ }
44
49
  const path = payload?.path;
45
50
  if (kind === "explorer" && typeof path === "string" && path) {
46
51
  const segments = path.split(/[\\/]/).filter(Boolean);
@@ -3,7 +3,7 @@
3
3
  import type { Rect } from "./window-geometry";
4
4
 
5
5
  /** Content families the layer can host. The registry maps each kind to its body component. */
6
- export type WindowKind = "explorer";
6
+ export type WindowKind = "explorer" | "team-member";
7
7
 
8
8
  export type WindowVisualState = "normal" | "maximized" | "minimized";
9
9