decocms 3.3.2 → 3.4.1

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 (268) hide show
  1. package/dist/client/assets/{AlertCircle-f1EMyhcF.js → AlertCircle-Bc3r3AGq.js} +1 -1
  2. package/dist/client/assets/{ArrowUpRight-DDOS_CqL.js → ArrowUpRight-DfEIENBd.js} +1 -1
  3. package/dist/client/assets/{Check-CtI-OH3k.js → Check--VLhXi5l.js} +1 -1
  4. package/dist/client/assets/{CheckCircle-Dk0E48Vj.js → CheckCircle-7xc6-R3M.js} +1 -1
  5. package/dist/client/assets/{ChevronDown-BL2fNdeZ.js → ChevronDown-66ErjaPf.js} +1 -1
  6. package/dist/client/assets/{ChevronLeft-ItYaMHNU.js → ChevronLeft-DyW6EtsM.js} +1 -1
  7. package/dist/client/assets/{ChevronRight-TMlXoq-Z.js → ChevronRight-C87PRbBa.js} +1 -1
  8. package/dist/client/assets/{ChevronUp-O10NbvHe.js → ChevronUp-BuPeXu8C.js} +1 -1
  9. package/dist/client/assets/{Container-B1sUXs1U.js → Container-cZo3l6OX.js} +1 -1
  10. package/dist/client/assets/{DotsVertical-DNPNHBpl.js → DotsVertical-CQgc8nSx.js} +1 -1
  11. package/dist/client/assets/{Eye-DWIY-6KS.js → Eye-DHHjYDbX.js} +1 -1
  12. package/dist/client/assets/{FilterLines-CWRo79Yt.js → FilterLines-CWR_eJ7z.js} +1 -1
  13. package/dist/client/assets/{Globe01-Cjaw-3jR.js → Globe01-B10jCcNH.js} +1 -1
  14. package/dist/client/assets/{HardDrive-Ddx5WtDi.js → HardDrive-7KMC69UH.js} +1 -1
  15. package/dist/client/assets/{Key01-ChvR9ujt.js → Key01-CTKRB2Cd.js} +1 -1
  16. package/dist/client/assets/LayoutLeft-SQH1HcmI.js +1 -0
  17. package/dist/client/assets/{LinkExternal01-Bo28LxVY.js → LinkExternal01-CjT-4hM8.js} +1 -1
  18. package/dist/client/assets/{Monitor01-Cx9Nl0mU.js → Monitor01-svtG00ql.js} +1 -1
  19. package/dist/client/assets/{Palette-DkgVQjRz.js → Palette-CA_16qMN.js} +1 -1
  20. package/dist/client/assets/{Play-VGEgQ-36.js → Play-CrdarOgl.js} +1 -1
  21. package/dist/client/assets/{Plus-DXVOsj8b.js → Plus-C9icoBmX.js} +1 -1
  22. package/dist/client/assets/{RefreshCcw01-CxP3kNii.js → RefreshCcw01-D1tvoH2_.js} +1 -1
  23. package/dist/client/assets/{SearchMd-B9xeWi0e.js → SearchMd-Di6_vvQ1.js} +1 -1
  24. package/dist/client/assets/{Settings02-DYlioLKt.js → Settings02-BylJiOHK.js} +1 -1
  25. package/dist/client/assets/{Shield01-CLHpjaQL.js → Shield01-CDSs18-B.js} +1 -1
  26. package/dist/client/assets/{Star01-BT882_xb.js → Star01-D1DEXGAi.js} +1 -1
  27. package/dist/client/assets/{Stars01-DDxsFd_C.js → Stars01-UWs0NZ30.js} +1 -1
  28. package/dist/client/assets/{Stars02-lM9wDOP9.js → Stars02-CXAcgX2g.js} +1 -1
  29. package/dist/client/assets/{Sun-CNIfXy2-.js → Sun-CCiXRotp.js} +1 -1
  30. package/dist/client/assets/{SwitchHorizontal01-Den03c4j.js → SwitchHorizontal01-BU3C5ze8.js} +1 -1
  31. package/dist/client/assets/{Tool01-D7SzxWeU.js → Tool01-BMk00CcA.js} +1 -1
  32. package/dist/client/assets/{Trash01-C2MDqKmR.js → Trash01-MTKniuxn.js} +1 -1
  33. package/dist/client/assets/{Upload01-BTsf5tML.js → Upload01-DsrMMWgf.js} +1 -1
  34. package/dist/client/assets/{User01-B1vfX-f8.js → User01-DShN8gQ-.js} +1 -1
  35. package/dist/client/assets/{Users01-dpqTHJ7Y.js → Users01-Bvh39Ydu.js} +1 -1
  36. package/dist/client/assets/{Users03-e9b5gRLu.js → Users03--pQkY_Pm.js} +1 -1
  37. package/dist/client/assets/{X-DvNDI9Zw.js → X-D-FyhysP.js} +1 -1
  38. package/dist/client/assets/{XCircle-DBS00WN3.js → XCircle-Pgrh_mFJ.js} +1 -1
  39. package/dist/client/assets/{XClose-BdAeKt9F.js → XClose-CXixB7oH.js} +1 -1
  40. package/dist/client/assets/{Zap-aFVcxgaa.js → Zap-BcF-Iz_3.js} +1 -1
  41. package/dist/client/assets/{ZapSquare-CC0ClMfp.js → ZapSquare-BH297b52.js} +1 -1
  42. package/dist/client/assets/ZoomOut-qupe1GUj.js +1 -0
  43. package/dist/client/assets/{access-gate-B8AAPT1T.js → access-gate-b8qBeVwB.js} +1 -1
  44. package/dist/client/assets/{accordion-BFQ7Yu8X.js → accordion-bX483Ztz.js} +1 -1
  45. package/dist/client/assets/add-section-modal-wMptfoHV.js +1 -0
  46. package/dist/client/assets/{agent-capabilities-BE7hHGoA.js → agent-capabilities-ADaK_qqp.js} +1 -1
  47. package/dist/client/assets/agent-icon-6l7Nwe2K.js +1 -0
  48. package/dist/client/assets/{agent-icons-UJTit2wi.js → agent-icons-BDS9BBHh.js} +1 -1
  49. package/dist/client/assets/agents-list-BdFiPs3r.js +1 -0
  50. package/dist/client/assets/ai-providers-lM-GGoBl.js +1 -0
  51. package/dist/client/assets/{alert-DkX6LwPv.js → alert-Ta86Z4_L.js} +1 -1
  52. package/dist/client/assets/{alert-dialog-DKXzP7Bt.js → alert-dialog-_2G9ccD7.js} +1 -1
  53. package/dist/client/assets/app-editor-BJ_HJXFs.js +1 -0
  54. package/dist/client/assets/{auth-catchall-CmwVLqj-.js → auth-catchall-BdHpJwKo.js} +1 -1
  55. package/dist/client/assets/{auth-split-layout-COIVkHnQ.js → auth-split-layout-DVUC3dWq.js} +1 -1
  56. package/dist/client/assets/{automation-list-row-c065EJtO.js → automation-list-row-BNHDxEG_.js} +1 -1
  57. package/dist/client/assets/{automation-runs-CPH-3xtw.js → automation-runs-BbNb5m5Z.js} +1 -1
  58. package/dist/client/assets/automations-ChMgB7Hm.js +1 -0
  59. package/dist/client/assets/{avatar-ETg7Q-GU.js → avatar-KTqg6Pir.js} +1 -1
  60. package/dist/client/assets/{badge-C8j1DE3m.js → badge-jK5_JNPO.js} +1 -1
  61. package/dist/client/assets/brand-context-D1YLW65X.js +1 -0
  62. package/dist/client/assets/buckets-CZodJDYe.js +1 -0
  63. package/dist/client/assets/{calendar-CLbDOOHw.js → calendar-DFy-BKZq.js} +1 -1
  64. package/dist/client/assets/{capability-load-error-DVzMymKs.js → capability-load-error-DUP-Lw9V.js} +1 -1
  65. package/dist/client/assets/{card-lOxFSSkP.js → card-Ce7y-Nqu.js} +1 -1
  66. package/dist/client/assets/chat-context-CpKbwwmM.js +20 -0
  67. package/dist/client/assets/{checkbox-DWa4G7u9.js → checkbox-MO2_GITU.js} +1 -1
  68. package/dist/client/assets/{cli-auth-success-BGZNVK0p.js → cli-auth-success-XZzsr92m.js} +1 -1
  69. package/dist/client/assets/collection-detail-Bv6I7A_k.js +1 -0
  70. package/dist/client/assets/{collection-display-button-NMk2O1lx.js → collection-display-button-DzXqmtbd.js} +1 -1
  71. package/dist/client/assets/{collection-search-BPGQygd8.js → collection-search-CPYfTi84.js} +1 -1
  72. package/dist/client/assets/{collection-table-wrapper-BXPnPT96.js → collection-table-wrapper-DgzLrzuP.js} +1 -1
  73. package/dist/client/assets/{collection-tabs-C_qWo2-y.js → collection-tabs-CIsqNrxU.js} +1 -1
  74. package/dist/client/assets/{collections-NKJwWp8f.js → collections-B-PXiD2Y.js} +1 -1
  75. package/dist/client/assets/{command-B3U7wd2O.js → command-BHmAxNRB.js} +1 -1
  76. package/dist/client/assets/{connect-desktop-dialog-CqBwflr-.js → connect-desktop-dialog-BQZD1I8n.js} +1 -1
  77. package/dist/client/assets/{connection-card-DG_N0P2G.js → connection-card-BHzG8ppu.js} +1 -1
  78. package/dist/client/assets/connection-detail-BlmixFGl.js +1 -0
  79. package/dist/client/assets/{connection-form-helpers-D79c5tFA.js → connection-form-helpers-CgKn2eM9.js} +1 -1
  80. package/dist/client/assets/connections-D5zy3mYf.js +1 -0
  81. package/dist/client/assets/{constants-Oykg5_q9.js → constants-DI_u-hVk.js} +1 -1
  82. package/dist/client/assets/{decopilot-sse-pool-02ukeQqS.js → decopilot-sse-pool-DPWhf2bX.js} +1 -1
  83. package/dist/client/assets/{dialog-DCjJZndp.js → dialog-Bt3zXcK6.js} +1 -1
  84. package/dist/client/assets/{domain-settings-Jn9iDH8k.js → domain-settings-CZnFXOwr.js} +1 -1
  85. package/dist/client/assets/{drawer-BTJhbpa8.js → drawer-DeHC5s3g.js} +1 -1
  86. package/dist/client/assets/{dropdown-menu-BnsK64En.js → dropdown-menu-SwrAlbwN.js} +1 -1
  87. package/dist/client/assets/dynamic-plugin-layout-WDrQTfck.js +1 -0
  88. package/dist/client/assets/{empty-state-C-NWO5ph.js → empty-state-BJFB-AwN.js} +1 -1
  89. package/dist/client/assets/{empty-state-CKyi-9Wl.js → empty-state-DlCayRAK.js} +1 -1
  90. package/dist/client/assets/{extract-connection-data-AirIeyvr.js → extract-connection-data-CrMYEI5i.js} +1 -1
  91. package/dist/client/assets/features-BgkDxOlu.js +1 -0
  92. package/dist/client/assets/file-explorer-DrueRu5k.js +2 -0
  93. package/dist/client/assets/{file-type-icon-DM9XMqae.js → file-type-icon-BHF_Ovux.js} +1 -1
  94. package/dist/client/assets/{files-BuO1w5Lh.js → files-BCcd3t2S.js} +1 -1
  95. package/dist/client/assets/{form-DMPH0FOJ.js → form-8jBGaVW7.js} +1 -1
  96. package/dist/client/assets/general-BJkPBrz3.js +1 -0
  97. package/dist/client/assets/{generate-id-i1xx8q5L.js → generate-id-Bfx7nOJH.js} +1 -1
  98. package/dist/client/assets/{github-repo-picker-CTcBo2fn.js → github-repo-picker-F4vLHgJ-.js} +1 -1
  99. package/dist/client/assets/header-tab-button-DCSg63Wl.js +5 -0
  100. package/dist/client/assets/{hover-card-CxtN1xCB.js → hover-card-DcJvwauT.js} +1 -1
  101. package/dist/client/assets/{image-field-HoCNk-1B.js → image-field-cind3dwC.js} +1 -1
  102. package/dist/client/assets/index-BIANv7ur.js +1 -0
  103. package/dist/client/assets/{index-DtSBWsbe.js → index-Bac2tQWh.js} +1 -1
  104. package/dist/client/assets/{index-CALXjj7d.js → index-BrIVZCzt.js} +1 -1
  105. package/dist/client/assets/index-BzoFvbIH.js +1 -0
  106. package/dist/client/assets/{index-C7oVr4Gj.js → index-C0t_9xyr.js} +1 -1
  107. package/dist/client/assets/{index-N_rB2zyq.js → index-C1kxS2Io.js} +1 -1
  108. package/dist/client/assets/index-C3tcbzRj.js +3 -0
  109. package/dist/client/assets/{index-Bja6OpPs.js → index-C_oSBqFD.js} +3 -3
  110. package/dist/client/assets/index-CfGIr4vM.js +310 -0
  111. package/dist/client/assets/index-ChzbgMa2.js +1 -0
  112. package/dist/client/assets/index-D5dv4kfs.css +1 -0
  113. package/dist/client/assets/{index-CB2ubqvZ.js → index-DEWYXjIf.js} +3 -3
  114. package/dist/client/assets/{index-lU9yS_Jh.js → index-DML39Lt-.js} +1 -1
  115. package/dist/client/assets/{index-xAwaYY9z.js → index-DQj7btfC.js} +15 -15
  116. package/dist/client/assets/{index-C6M-_40T.js → index-DlcvFOU2.js} +1 -1
  117. package/dist/client/assets/{index-DUzjuDnX.js → index-d7-A3CDs.js} +1 -1
  118. package/dist/client/assets/{index-redirect-Bh1l7WCU.js → index-redirect-KnAJlqvx.js} +1 -1
  119. package/dist/client/assets/{infiniteQueryObserver-DFB-ygfX.js → infiniteQueryObserver-w8EJyyrI.js} +1 -1
  120. package/dist/client/assets/{input-D9gxUyjr.js → input-Ct22kLSY.js} +1 -1
  121. package/dist/client/assets/{integration-icon-BffTGZN4.js → integration-icon-CNf7sZ5b.js} +1 -1
  122. package/dist/client/assets/{label-BL0BsZ1B.js → label-CvDTa89d.js} +1 -1
  123. package/dist/client/assets/{layout-CfSS4bsq.js → layout-Dkxj2bFg.js} +1 -1
  124. package/dist/client/assets/{login-BuZSwsso.js → login-Dv6Rz1bY.js} +1 -1
  125. package/dist/client/assets/{members-D7WrKG_A.js → members-BQXsk1m4.js} +2 -2
  126. package/dist/client/assets/{monaco-editor-DtMcxLSi.js → monaco-editor-DR4yoUjc.js} +1 -1
  127. package/dist/client/assets/{monitoring-stats-row-fp6agr8k.js → monitoring-stats-row-Co70p3-n.js} +1 -1
  128. package/dist/client/assets/{oauth-callback-d6LUyXrn.js → oauth-callback-CQPHoHy-.js} +1 -1
  129. package/dist/client/assets/{oauth-callback-ai-provider-CkKiZhn4.js → oauth-callback-ai-provider-BJeYyFlX.js} +1 -1
  130. package/dist/client/assets/{onboarding-DfbOcrIX.js → onboarding-CzNreyu4.js} +1 -1
  131. package/dist/client/assets/{org-install-CFDYPwCg.js → org-install-aZxuYxoI.js} +1 -1
  132. package/dist/client/assets/{org-layout-WeQp4B6-.js → org-layout-CR6iFjC9.js} +1 -1
  133. package/dist/client/assets/org-plugin-layout-Ah6P36_M.js +1 -0
  134. package/dist/client/assets/{page-seo-form-BAg1yciU.js → page-seo-form-BzX1ts5p.js} +1 -1
  135. package/dist/client/assets/page-seo-sheet-v7xZnlYi.js +1 -0
  136. package/dist/client/assets/pair--AN1yq4g.js +39 -0
  137. package/dist/client/assets/{plugin-empty-state-9CxATRN3.js → plugin-empty-state-CIZ1bBrP.js} +1 -1
  138. package/dist/client/assets/{plugin-header-7GRdizIh.js → plugin-header-CExvgCKI.js} +1 -1
  139. package/dist/client/assets/{plugin-layout-CJOy-tZB.js → plugin-layout-DS-orBC-.js} +1 -1
  140. package/dist/client/assets/{popover-OiarPewu.js → popover-DXn7orE4.js} +1 -1
  141. package/dist/client/assets/{post-editor-BQ_9KZ48.js → post-editor-bszZQHpg.js} +3 -3
  142. package/dist/client/assets/{primitives-pd_yvF0S.js → primitives-CIXky-Gt.js} +1 -1
  143. package/dist/client/assets/profile-CZWeJMEP.js +1 -0
  144. package/dist/client/assets/project-app-view-DJMvDwFa.js +1 -0
  145. package/dist/client/assets/record-editor-C5KWfSLg.js +1 -0
  146. package/dist/client/assets/registry-CX2hi2E3.js +2 -0
  147. package/dist/client/assets/{registry-layout-D6FuqVAe.js → registry-layout-DjFUi3yC.js} +1 -1
  148. package/dist/client/assets/{require-capability-DVkbMmft.js → require-capability-DDbNSPG5.js} +1 -1
  149. package/dist/client/assets/{required-auth-layout-CRGKVRTF.js → required-auth-layout-Bpv6G5uE.js} +1 -1
  150. package/dist/client/assets/{reset-password-DsVXpYD9.js → reset-password-D1T0PiTj.js} +1 -1
  151. package/dist/client/assets/{resizable-a1gJgrB1.js → resizable-Bi3jkg9o.js} +1 -1
  152. package/dist/client/assets/roles-BBWw8puD.js +1 -0
  153. package/dist/client/assets/{save-status-DRXktsXS.js → save-status-PzXq6KUK.js} +1 -1
  154. package/dist/client/assets/{schema-form-DvpyibqS.js → schema-form-CM7pfylQ.js} +1 -1
  155. package/dist/client/assets/{scroll-area-Ds73tc0J.js → scroll-area-BNzrPnq5.js} +1 -1
  156. package/dist/client/assets/{search-input-BNN911DE.js → search-input-C76EA-t6.js} +1 -1
  157. package/dist/client/assets/secrets-DMEa7GIW.js +1 -0
  158. package/dist/client/assets/{sections-editor-6P4Yegkk.js → sections-editor--GuZPQzC.js} +2 -2
  159. package/dist/client/assets/{select-fwtyEN3G.js → select-D_4UU9rK.js} +1 -1
  160. package/dist/client/assets/{select-model-D7RFa4up.js → select-model-cV_VUU8G.js} +1 -1
  161. package/dist/client/assets/seo-editor-fhccoPNR.js +1 -0
  162. package/dist/client/assets/settings-layout-DH37q17x.js +1 -0
  163. package/dist/client/assets/{settings-section-T0fDyncu.js → settings-section-DsXtuKDY.js} +1 -1
  164. package/dist/client/assets/sheet-D01YX9h0.js +1 -0
  165. package/dist/client/assets/shell-controls-CNVI6ZJJ.js +1 -0
  166. package/dist/client/assets/{shell-layout-I9tl7R9J.js → shell-layout-xPMsY5hC.js} +1 -1
  167. package/dist/client/assets/{shell-route-loading-CdGn4Kpm.js → shell-route-loading-jPQ9XajT.js} +1 -1
  168. package/dist/client/assets/sidebar-CJNdWlwy.js +1 -0
  169. package/dist/client/assets/{skeleton-CM-Pi0cm.js → skeleton-DEf5lpEA.js} +1 -1
  170. package/dist/client/assets/{sortable.esm-KFDeRhD3.js → sortable.esm-KhkNVCRe.js} +1 -1
  171. package/dist/client/assets/{spinner-D4UjUyLz.js → spinner-srK7FwNe.js} +1 -1
  172. package/dist/client/assets/sso-BLXKmxlD.js +1 -0
  173. package/dist/client/assets/store-DydXTg6V.js +1 -0
  174. package/dist/client/assets/store-registry-BJqdtxTv.js +2 -0
  175. package/dist/client/assets/{switch-DB7mlnDa.js → switch-Cbu6JSI9.js} +1 -1
  176. package/dist/client/assets/{tab-id-BFVzx5fb.js → tab-id-CbYpEVAZ.js} +11 -11
  177. package/dist/client/assets/{table-Cd_YQk4M.js → table-dr9K2jWM.js} +1 -1
  178. package/dist/client/assets/{tabs-Dsmq9_wP.js → tabs-CJU0nxr6.js} +1 -1
  179. package/dist/client/assets/{task-status-C990ALOP.js → task-status-Dyf8qaxz.js} +1 -1
  180. package/dist/client/assets/{textarea-DGZy_omr.js → textarea-BquesfD_.js} +1 -1
  181. package/dist/client/assets/{toggle-group-BkLJ1frl.js → toggle-group-m3q7pubc.js} +1 -1
  182. package/dist/client/assets/{toolbar-Bnt0c2J3.js → toolbar-Bkxu3n7s.js} +1 -1
  183. package/dist/client/assets/{tools-list-Dpd5zOg1.js → tools-list-CG_BLK1j.js} +1 -1
  184. package/dist/client/assets/{tooltip-BvIsFjJR.js → tooltip-Bm-whGHF.js} +1 -1
  185. package/dist/client/assets/{types-Dn1K45P2.js → types-Bkz2p_os.js} +1 -1
  186. package/dist/client/assets/{use-ai-providers-BQwBnNI3.js → use-ai-providers-B82V9Np8.js} +1 -1
  187. package/dist/client/assets/{use-automations-BEPg-aek.js → use-automations-B5qTxkL8.js} +1 -1
  188. package/dist/client/assets/{use-capability-rrurYBXm.js → use-capability-DpFGw1TU.js} +1 -1
  189. package/dist/client/assets/{use-collections-e25i0NM-.js → use-collections-iUV-5kFm.js} +1 -1
  190. package/dist/client/assets/{use-connection-BQxCYR_o.js → use-connection-DTKE65yR.js} +1 -1
  191. package/dist/client/assets/{use-copy-BKbXbMuY.js → use-copy-Dr7XRETN.js} +1 -1
  192. package/dist/client/assets/{use-create-virtual-mcp-trxCQUHO.js → use-create-virtual-mcp-DlMteKXP.js} +1 -1
  193. package/dist/client/assets/{use-debounced-autosave-Oryp4cAP.js → use-debounced-autosave-DFapff8c.js} +1 -1
  194. package/dist/client/assets/{use-delete-connection-DEEK0oTT.js → use-delete-connection-CU96IyMZ.js} +1 -1
  195. package/dist/client/assets/{use-file-configs-Qrd6eZyZ.js → use-file-configs-C8ipJXZL.js} +1 -1
  196. package/dist/client/assets/{use-infinite-scroll-DcQNoJ7_.js → use-infinite-scroll-KU0QCoEE.js} +1 -1
  197. package/dist/client/assets/{use-list-state-BoOb09L9.js → use-list-state-DtwRJx6R.js} +1 -1
  198. package/dist/client/assets/{use-mcp-prompts-D9JZQ88U.js → use-mcp-prompts-aLA3FcF_.js} +1 -1
  199. package/dist/client/assets/{use-mcp-tools-DkcEQK3V.js → use-mcp-tools-CK1DUQVb.js} +1 -1
  200. package/dist/client/assets/{use-members-DERsL77r.js → use-members-C9apz-8a.js} +1 -1
  201. package/dist/client/assets/{use-navigate-to-agent-sJoZ-Wbv.js → use-navigate-to-agent-DRRtr51U.js} +1 -1
  202. package/dist/client/assets/{use-org-auth-client-CF8P1btO.js → use-org-auth-client-CI8XyYuM.js} +1 -1
  203. package/dist/client/assets/{use-org-sso-D2JNbrnR.js → use-org-sso-CYBgAyDV.js} +1 -1
  204. package/dist/client/assets/{use-organization-roles-Ca1byRHM.js → use-organization-roles-C5ABG30J.js} +1 -1
  205. package/dist/client/assets/{use-organization-settings-CVmm-jyI.js → use-organization-settings-B4ywx3QZ.js} +1 -1
  206. package/dist/client/assets/{use-registry-connections-B6OVBI_G.js → use-registry-connections-B-c9LId-.js} +1 -1
  207. package/dist/client/assets/{use-secrets-_k7d1I4Z.js → use-secrets-D3irvtLI.js} +1 -1
  208. package/dist/client/assets/{use-status-sounds-gVglgYnC.js → use-status-sounds-BEbMXd3N.js} +1 -1
  209. package/dist/client/assets/{use-view-mode-DdD0TD2N.js → use-view-mode-Jd_CE1Mx.js} +1 -1
  210. package/dist/client/assets/{use-virtual-mcp-BNfSbQBW.js → use-virtual-mcp-DsdNWb58.js} +1 -1
  211. package/dist/client/assets/useInfiniteQuery-DrZL_IGs.js +1 -0
  212. package/dist/client/assets/{useRouterState-CI4Wmw4H.js → useRouterState-CU2GqlKv.js} +1 -1
  213. package/dist/client/assets/useSuspenseInfiniteQuery-CN1mQR7i.js +1 -0
  214. package/dist/client/assets/{user-ChT4iOGQ.js → user-sLZtiN2i.js} +1 -1
  215. package/dist/client/assets/{view-mode-toggle-DCljG_Km.js → view-mode-toggle-DSmCr2tD.js} +1 -1
  216. package/dist/client/assets/workflow-BK9KKo6T.js +1 -0
  217. package/dist/client/assets/workflow-detail-DzASvQVR.js +1 -0
  218. package/dist/client/deck-runtime/v1/deck-viewer.js +1516 -0
  219. package/dist/client/index.html +2 -2
  220. package/dist/server/cli.js +542 -539
  221. package/dist/server/migrate.js +90 -90
  222. package/dist/server/server.js +287 -287
  223. package/package.json +1 -1
  224. package/dist/client/assets/ZoomOut-Bj5jamAx.js +0 -1
  225. package/dist/client/assets/add-section-modal-BmCzDi10.js +0 -1
  226. package/dist/client/assets/agent-icon-D4vfmbIE.js +0 -1
  227. package/dist/client/assets/agents-list-LpRFwfeM.js +0 -1
  228. package/dist/client/assets/ai-providers-BqYvRcM4.js +0 -1
  229. package/dist/client/assets/app-editor-CIaq7eCO.js +0 -1
  230. package/dist/client/assets/automations-jHIRR-Gp.js +0 -1
  231. package/dist/client/assets/brand-context-pJc99xfz.js +0 -1
  232. package/dist/client/assets/buckets-CD20kw5y.js +0 -1
  233. package/dist/client/assets/chat-context-Bnval_3z.js +0 -20
  234. package/dist/client/assets/collection-detail-BQV4fFQi.js +0 -1
  235. package/dist/client/assets/connection-detail-BxZdoV6Y.js +0 -1
  236. package/dist/client/assets/connections-BKRkcjPX.js +0 -1
  237. package/dist/client/assets/dynamic-plugin-layout-DI1tAQQN.js +0 -1
  238. package/dist/client/assets/features-CCh1HKhB.js +0 -1
  239. package/dist/client/assets/file-explorer-BQmgDEDi.js +0 -2
  240. package/dist/client/assets/general-BorJGZAV.js +0 -1
  241. package/dist/client/assets/header-tab-button-CJMFloVi.js +0 -3
  242. package/dist/client/assets/index-BNc5J02Q.js +0 -1
  243. package/dist/client/assets/index-CTo6iOJE.js +0 -1
  244. package/dist/client/assets/index-DRkA4nnL.js +0 -3
  245. package/dist/client/assets/index-DzP6PZZ7.css +0 -1
  246. package/dist/client/assets/index-tZSWGe84.js +0 -1
  247. package/dist/client/assets/index-t_7wQBv1.js +0 -310
  248. package/dist/client/assets/org-plugin-layout-CtY6eOb7.js +0 -1
  249. package/dist/client/assets/page-seo-sheet-CG4OfDmr.js +0 -1
  250. package/dist/client/assets/pair-CCKXkNYO.js +0 -39
  251. package/dist/client/assets/profile-BQDabc6t.js +0 -1
  252. package/dist/client/assets/project-app-view-B6xEMwDB.js +0 -1
  253. package/dist/client/assets/record-editor-DeENCgCd.js +0 -1
  254. package/dist/client/assets/registry-C8DHcJ9B.js +0 -2
  255. package/dist/client/assets/roles-CX312B39.js +0 -1
  256. package/dist/client/assets/secrets-BeeL9rnH.js +0 -1
  257. package/dist/client/assets/seo-editor-CsFpa8ZP.js +0 -1
  258. package/dist/client/assets/settings-layout-DwCpTLea.js +0 -1
  259. package/dist/client/assets/sheet-IM6_8Cdx.js +0 -1
  260. package/dist/client/assets/shell-controls-VlzjU05Z.js +0 -1
  261. package/dist/client/assets/sidebar-CAkfGpdS.js +0 -1
  262. package/dist/client/assets/sso-B5mHZmOT.js +0 -1
  263. package/dist/client/assets/store-DVYNpmdS.js +0 -1
  264. package/dist/client/assets/store-registry-DRdcwU2X.js +0 -2
  265. package/dist/client/assets/useInfiniteQuery-Bj3r3xlf.js +0 -1
  266. package/dist/client/assets/useSuspenseInfiniteQuery-BbAiO9h1.js +0 -1
  267. package/dist/client/assets/workflow-BbaQXvps.js +0 -1
  268. package/dist/client/assets/workflow-detail-CU9YTyf_.js +0 -1
@@ -0,0 +1,1516 @@
1
+ /**
2
+ * <deck-viewer> — Studio's presentation deck runtime (v1).
3
+ *
4
+ * A deck is a single self-contained HTML file: a theme <style> in <head>,
5
+ * this script (classic, non-module — loaded no-cors so it works from the
6
+ * sandboxed preview iframe), and one <deck-viewer> element whose direct
7
+ * <section> children are the slides.
8
+ *
9
+ * <deck-viewer width="1920" height="1080">
10
+ * <section>…</section>
11
+ * <section>…</section>
12
+ * </deck-viewer>
13
+ * <script src="/deck-runtime/v1/deck-viewer.js"></script>
14
+ *
15
+ * Features:
16
+ * - Fixed design canvas (default 1920×1080) scaled with transform:scale()
17
+ * to fit the viewport, letterboxed.
18
+ * - Keyboard nav (←/→, PgUp/PgDn, Space, Home/End, digits, R), tap-halves
19
+ * nav on touch, bottom-center overlay with prev/count/next/reset.
20
+ * - Thumbnail rail (left column of static scaled clones); click to
21
+ * navigate, drag to reorder, right-click for Skip / Move / Duplicate /
22
+ * Delete — structural ops are only enabled in edit mode.
23
+ * - Edit mode (host-toggled): slides become contenteditable; text edits
24
+ * and structural ops self-apply optimistically AND are posted to the
25
+ * parent window, which persists them to the source file.
26
+ * - Print: one page per slide at design size (@page rule injected into
27
+ * document head — @page is a no-op inside shadow DOM). Open with a
28
+ * #print fragment to auto-trigger window.print() (PDF export path).
29
+ *
30
+ * postMessage protocol (v1) — all messages carry `v: 1`. The document may
31
+ * run in an opaque-origin sandboxed iframe, so both directions post with
32
+ * targetOrigin "*" and the parent validates event.source identity.
33
+ *
34
+ * runtime → parent:
35
+ * { v, source: "deck-viewer", type: "ready", total, design: {width, height} }
36
+ * { v, source: "deck-viewer", type: "state", index, total, skipped: number[] }
37
+ * { v, source: "deck-viewer", type: "op", opId, witness: { childCount }, op }
38
+ * op: { kind: "move", from, to } | { kind: "remove", at }
39
+ * | { kind: "duplicate", at }
40
+ * | { kind: "set-attr", at, name, value } | { kind: "remove-attr", at, name }
41
+ * | { kind: "replace", at, html } // contenteditable edit; cleaned outerHTML
42
+ *
43
+ * parent → runtime:
44
+ * { v, type: "ack", opId, ok, error? }
45
+ * { v, type: "set-edit-mode", enabled }
46
+ * { v, type: "goto", index }
47
+ * { v, type: "print" }
48
+ *
49
+ * Slide indices are positions in the light-DOM section list (including
50
+ * skipped slides) at the moment the op was emitted.
51
+ */
52
+
53
+ (() => {
54
+ const PROTOCOL_V = 1;
55
+ const DESIGN_W_DEFAULT = 1920;
56
+ const DESIGN_H_DEFAULT = 1080;
57
+ const OVERLAY_HIDE_MS = 1800;
58
+ const ACK_TIMEOUT_MS = 5000;
59
+ const TEXT_EDIT_DEBOUNCE_MS = 800;
60
+ const RAIL_DEFAULT_W = 168;
61
+ const FINE_POINTER_MQ = matchMedia("(hover: hover) and (pointer: fine)");
62
+ const NARROW_MQ = matchMedia("(max-width: 640px)");
63
+ // Slide-authored controls that should keep a tap instead of navigating.
64
+ const INTERACTIVE_SEL =
65
+ 'a[href], button, input, select, textarea, summary, label, video[controls], audio[controls], [role="button"], [onclick]';
66
+ // Attributes this runtime writes onto slides; stripped before any HTML
67
+ // leaves the component (replace ops, duplicate clones) so the persisted
68
+ // file stays clean.
69
+ const RUNTIME_ATTRS = [
70
+ "contenteditable",
71
+ "spellcheck",
72
+ "tabindex",
73
+ "aria-hidden",
74
+ "data-deck-active",
75
+ "data-deck-last-visible",
76
+ ];
77
+
78
+ const stylesheet = `
79
+ :host {
80
+ position: fixed;
81
+ inset: 0;
82
+ display: block;
83
+ background: #111;
84
+ color: #fff;
85
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
86
+ overflow: hidden;
87
+ -webkit-tap-highlight-color: transparent;
88
+ }
89
+
90
+ .stage {
91
+ position: absolute;
92
+ inset: 0;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ }
97
+ .canvas {
98
+ position: relative;
99
+ transform-origin: center center;
100
+ flex-shrink: 0;
101
+ background: #fff;
102
+ will-change: transform;
103
+ }
104
+
105
+ /* Slides live in light DOM (via <slot>) so authored CSS applies. Every
106
+ slotted child is absolutely positioned to stack; only the active one
107
+ is visible. Hidden, not unmounted — slide state survives nav. */
108
+ ::slotted(*) {
109
+ position: absolute !important;
110
+ inset: 0 !important;
111
+ width: 100% !important;
112
+ height: 100% !important;
113
+ box-sizing: border-box !important;
114
+ overflow: hidden;
115
+ opacity: 0;
116
+ pointer-events: none;
117
+ visibility: hidden;
118
+ }
119
+ ::slotted([data-deck-active]) {
120
+ opacity: 1;
121
+ pointer-events: auto;
122
+ visibility: visible;
123
+ }
124
+
125
+ .overlay {
126
+ position: fixed;
127
+ left: 50%;
128
+ bottom: 20px;
129
+ transform: translate(-50%, 6px);
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 2px;
133
+ padding: 4px;
134
+ background: rgba(0, 0, 0, 0.92);
135
+ color: #fff;
136
+ border-radius: 999px;
137
+ font-size: 12px;
138
+ opacity: 0;
139
+ pointer-events: none;
140
+ transition: opacity 200ms ease, transform 200ms ease;
141
+ z-index: 100;
142
+ user-select: none;
143
+ }
144
+ .overlay[data-visible] {
145
+ opacity: 1;
146
+ pointer-events: auto;
147
+ transform: translate(-50%, 0);
148
+ }
149
+ .btn {
150
+ appearance: none;
151
+ background: transparent;
152
+ border: 0;
153
+ margin: 0;
154
+ padding: 0;
155
+ color: rgba(255, 255, 255, 0.75);
156
+ font: inherit;
157
+ display: inline-flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ height: 28px;
161
+ min-width: 28px;
162
+ border-radius: 999px;
163
+ cursor: pointer;
164
+ }
165
+ .btn:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }
166
+ .btn svg { width: 14px; height: 14px; display: block; }
167
+ .count {
168
+ font-variant-numeric: tabular-nums;
169
+ padding: 0 8px;
170
+ min-width: 44px;
171
+ text-align: center;
172
+ }
173
+ .count .total { color: rgba(255, 255, 255, 0.55); }
174
+
175
+ /* ── Thumbnail rail ─────────────────────────────────────────────── */
176
+ .rail {
177
+ position: fixed;
178
+ left: 0;
179
+ top: 0;
180
+ bottom: 0;
181
+ width: var(--deck-rail-w, ${RAIL_DEFAULT_W}px);
182
+ background: #1a1a1a;
183
+ border-right: 1px solid rgba(255, 255, 255, 0.08);
184
+ overflow-y: auto;
185
+ overflow-x: hidden;
186
+ padding: 12px 10px;
187
+ box-sizing: border-box;
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 12px;
191
+ z-index: 50;
192
+ scrollbar-width: thin;
193
+ scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
194
+ }
195
+ /* The rail is presentation chrome — hidden by default (a deck opened
196
+ in a fresh tab is presentation-first). The 'rail' URL-hash token or
197
+ the host's set-rail message opens it (data-rail-open). */
198
+ .rail, .rail-resize { display: none; }
199
+ :host([data-rail-open]) .rail { display: flex; }
200
+ :host([data-rail-open]) .rail-resize { display: block; }
201
+ :host([no-rail]) .rail, :host([no-rail]) .rail-resize { display: none !important; }
202
+ @media (max-width: 640px) {
203
+ .rail, .rail-resize { display: none !important; }
204
+ }
205
+ .thumb {
206
+ position: relative;
207
+ display: flex;
208
+ align-items: flex-start;
209
+ gap: 8px;
210
+ cursor: pointer;
211
+ user-select: none;
212
+ outline: none;
213
+ }
214
+ .thumb .num {
215
+ width: 16px;
216
+ flex-shrink: 0;
217
+ font-size: 11px;
218
+ text-align: right;
219
+ color: rgba(255, 255, 255, 0.55);
220
+ padding-top: 2px;
221
+ font-variant-numeric: tabular-nums;
222
+ }
223
+ .thumb .frame {
224
+ position: relative;
225
+ flex: 1;
226
+ min-width: 0;
227
+ aspect-ratio: var(--deck-aspect, 16 / 9);
228
+ background: #fff;
229
+ border-radius: 4px;
230
+ outline: 2px solid transparent;
231
+ overflow: hidden;
232
+ transition: outline-color 120ms ease;
233
+ }
234
+ .thumb:hover .frame { outline-color: rgba(255, 255, 255, 0.25); }
235
+ .thumb[data-current] .num { color: #fff; }
236
+ .thumb[data-current] .frame { outline-color: #6e8bff; }
237
+ .thumb[data-dragging] { opacity: 0.35; }
238
+ .thumb::before {
239
+ content: "";
240
+ position: absolute;
241
+ left: 24px;
242
+ right: 0;
243
+ height: 3px;
244
+ border-radius: 2px;
245
+ background: #6e8bff;
246
+ opacity: 0;
247
+ pointer-events: none;
248
+ }
249
+ .thumb[data-drop="before"]::before { top: -8px; opacity: 1; }
250
+ .thumb[data-drop="after"]::before { bottom: -8px; opacity: 1; }
251
+ .thumb[data-skip] .frame { opacity: 0.35; }
252
+ .thumb[data-skip] .frame::after {
253
+ content: "Skipped";
254
+ position: absolute;
255
+ inset: 0;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ background: rgba(0, 0, 0, 0.45);
260
+ color: #fff;
261
+ font-size: 10px;
262
+ letter-spacing: 0.04em;
263
+ }
264
+ .rail-resize {
265
+ position: fixed;
266
+ left: calc(var(--deck-rail-w, ${RAIL_DEFAULT_W}px) - 3px);
267
+ top: 0;
268
+ bottom: 0;
269
+ width: 6px;
270
+ cursor: col-resize;
271
+ z-index: 60;
272
+ touch-action: none;
273
+ }
274
+ .rail-resize:hover, .rail-resize[data-dragging] { background: rgba(255, 255, 255, 0.12); }
275
+
276
+ .ctxmenu {
277
+ position: fixed;
278
+ min-width: 150px;
279
+ padding: 4px;
280
+ background: #242424;
281
+ border: 1px solid rgba(255, 255, 255, 0.12);
282
+ border-radius: 7px;
283
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
284
+ z-index: 120;
285
+ display: none;
286
+ font-size: 12px;
287
+ }
288
+ .ctxmenu[data-open] { display: block; }
289
+ .ctxmenu button {
290
+ display: block;
291
+ width: 100%;
292
+ appearance: none;
293
+ border: 0;
294
+ background: transparent;
295
+ color: #e8e8e8;
296
+ font: inherit;
297
+ text-align: left;
298
+ padding: 6px 10px;
299
+ border-radius: 4px;
300
+ cursor: pointer;
301
+ }
302
+ .ctxmenu button:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); }
303
+ .ctxmenu button:disabled { opacity: 0.35; cursor: default; }
304
+ .ctxmenu hr { border: 0; border-top: 1px solid rgba(255, 255, 255, 0.1); margin: 4px 2px; }
305
+
306
+ .confirm-backdrop {
307
+ position: fixed;
308
+ inset: 0;
309
+ background: rgba(0, 0, 0, 0.45);
310
+ z-index: 130;
311
+ display: none;
312
+ align-items: center;
313
+ justify-content: center;
314
+ }
315
+ .confirm-backdrop[data-open] { display: flex; }
316
+ .confirm {
317
+ width: 300px;
318
+ max-width: calc(100vw - 32px);
319
+ background: #2a2a2a;
320
+ color: #e8e8e8;
321
+ border: 1px solid rgba(255, 255, 255, 0.12);
322
+ border-radius: 12px;
323
+ overflow: hidden;
324
+ }
325
+ .confirm .body { padding: 18px 18px 14px; }
326
+ .confirm .title { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
327
+ .confirm .msg { font-size: 13px; line-height: 1.5; color: rgba(255, 255, 255, 0.65); }
328
+ .confirm .footer {
329
+ padding: 12px 18px;
330
+ background: #1f1f1f;
331
+ display: flex;
332
+ justify-content: flex-end;
333
+ gap: 8px;
334
+ }
335
+ .confirm button {
336
+ appearance: none;
337
+ font: inherit;
338
+ font-size: 13px;
339
+ padding: 7px 14px;
340
+ border-radius: 8px;
341
+ cursor: pointer;
342
+ border: 0;
343
+ }
344
+ .confirm .cancel { background: transparent; color: rgba(255, 255, 255, 0.8); }
345
+ .confirm .cancel:hover { background: rgba(255, 255, 255, 0.08); }
346
+ .confirm .danger { background: #c0442c; color: #fff; }
347
+ .confirm .danger:hover { background: #ad3c26; }
348
+
349
+ .toast {
350
+ position: fixed;
351
+ left: 50%;
352
+ bottom: 64px;
353
+ transform: translateX(-50%);
354
+ background: #c0442c;
355
+ color: #fff;
356
+ font-size: 12px;
357
+ padding: 8px 14px;
358
+ border-radius: 8px;
359
+ z-index: 140;
360
+ opacity: 0;
361
+ pointer-events: none;
362
+ transition: opacity 200ms ease;
363
+ }
364
+ .toast[data-visible] { opacity: 1; }
365
+
366
+ /* Edit-mode affordance: a subtle outline on the hovered/focused slide. */
367
+ :host([data-edit-mode]) ::slotted([data-deck-active]) {
368
+ outline: 1px dashed rgba(110, 139, 255, 0.5);
369
+ outline-offset: -1px;
370
+ }
371
+
372
+ /* ── Print: one page per slide, no chrome ───────────────────────────
373
+ Screen layout stacks slides inside a scaled canvas; for print they
374
+ go into document flow at the authored design size so the browser
375
+ paginates one slide per sheet. The @page rule is injected into
376
+ document <head> (no effect inside shadow DOM). */
377
+ @media print {
378
+ :host {
379
+ position: static;
380
+ inset: auto;
381
+ background: none;
382
+ overflow: visible;
383
+ color: inherit;
384
+ }
385
+ .stage { position: static; display: block; }
386
+ .canvas {
387
+ transform: none !important;
388
+ width: auto !important;
389
+ height: auto !important;
390
+ background: none;
391
+ will-change: auto;
392
+ }
393
+ ::slotted(*) {
394
+ position: relative !important;
395
+ inset: auto !important;
396
+ width: var(--deck-design-w) !important;
397
+ height: var(--deck-design-h) !important;
398
+ box-sizing: border-box !important;
399
+ opacity: 1 !important;
400
+ visibility: visible !important;
401
+ pointer-events: auto;
402
+ break-after: page;
403
+ page-break-after: always;
404
+ break-inside: avoid;
405
+ overflow: hidden;
406
+ }
407
+ /* The last *visible* slide must not force a trailing blank page —
408
+ :last-child alone misses the case where trailing slides are
409
+ skipped. _markLastVisible() maintains the attribute. */
410
+ ::slotted(*:last-child),
411
+ ::slotted([data-deck-last-visible]) {
412
+ break-after: auto;
413
+ page-break-after: auto;
414
+ }
415
+ ::slotted([data-deck-skip]) { display: none !important; }
416
+ .overlay, .rail, .rail-resize, .ctxmenu, .confirm-backdrop, .toast {
417
+ display: none !important;
418
+ }
419
+ }
420
+ `;
421
+
422
+ class DeckViewer extends HTMLElement {
423
+ static get observedAttributes() {
424
+ return ["width", "height", "no-rail"];
425
+ }
426
+
427
+ constructor() {
428
+ super();
429
+ this._root = this.attachShadow({ mode: "open" });
430
+ this._index = 0;
431
+ this._slides = [];
432
+ this._thumbs = [];
433
+ this._editMode = false;
434
+ this._railOpen = false;
435
+ this._opSeq = 0;
436
+ this._pendingAcks = new Map();
437
+ this._railLocked = false;
438
+ this._menuIndex = -1;
439
+ this._confirmIndex = -1;
440
+ this._dragFrom = null;
441
+ this._dropOn = null;
442
+ this._hideTimer = null;
443
+ this._printed = false;
444
+
445
+ this._onKey = this._onKey.bind(this);
446
+ this._onResize = this._onResize.bind(this);
447
+ this._onMessage = this._onMessage.bind(this);
448
+ this._onMouseMove = this._onMouseMove.bind(this);
449
+ this._onTap = this._onTap.bind(this);
450
+ this._onHashChange = this._onHashChange.bind(this);
451
+ this._onBeforePrint = this._onBeforePrint.bind(this);
452
+ this._onAfterPrint = this._onAfterPrint.bind(this);
453
+ this._onFocusIn = this._onFocusIn.bind(this);
454
+ this._onFocusOut = this._onFocusOut.bind(this);
455
+ this._onInput = this._onInput.bind(this);
456
+ this._onDocClick = (e) => {
457
+ if (this._menu && e.composedPath().includes(this._menu)) return;
458
+ this._closeMenu();
459
+ };
460
+ }
461
+
462
+ get designWidth() {
463
+ return parseInt(this.getAttribute("width"), 10) || DESIGN_W_DEFAULT;
464
+ }
465
+ get designHeight() {
466
+ return parseInt(this.getAttribute("height"), 10) || DESIGN_H_DEFAULT;
467
+ }
468
+
469
+ connectedCallback() {
470
+ this._render();
471
+ this._syncPrintPageRule();
472
+ window.addEventListener("keydown", this._onKey);
473
+ window.addEventListener("resize", this._onResize);
474
+ window.addEventListener("message", this._onMessage);
475
+ window.addEventListener("mousemove", this._onMouseMove, {
476
+ passive: true,
477
+ });
478
+ window.addEventListener("hashchange", this._onHashChange);
479
+ window.addEventListener("beforeprint", this._onBeforePrint);
480
+ window.addEventListener("afterprint", this._onAfterPrint);
481
+ window.addEventListener("click", this._onDocClick, true);
482
+ this.addEventListener("click", this._onTap);
483
+ this.addEventListener("focusin", this._onFocusIn);
484
+ this.addEventListener("focusout", this._onFocusOut);
485
+ this.addEventListener("input", this._onInput);
486
+ // Initial slide collection happens via slotchange (fires on mount).
487
+ this._maybeAutoPrint();
488
+ }
489
+
490
+ disconnectedCallback() {
491
+ window.removeEventListener("keydown", this._onKey);
492
+ window.removeEventListener("resize", this._onResize);
493
+ window.removeEventListener("message", this._onMessage);
494
+ window.removeEventListener("mousemove", this._onMouseMove);
495
+ window.removeEventListener("hashchange", this._onHashChange);
496
+ window.removeEventListener("beforeprint", this._onBeforePrint);
497
+ window.removeEventListener("afterprint", this._onAfterPrint);
498
+ window.removeEventListener("click", this._onDocClick, true);
499
+ this.removeEventListener("click", this._onTap);
500
+ this.removeEventListener("focusin", this._onFocusIn);
501
+ this.removeEventListener("focusout", this._onFocusOut);
502
+ this.removeEventListener("input", this._onInput);
503
+ if (this._hideTimer) clearTimeout(this._hideTimer);
504
+ if (this._textDebounce) clearTimeout(this._textDebounce);
505
+ this._pendingAcks.forEach((p) => clearTimeout(p.timer));
506
+ this._pendingAcks.clear();
507
+ }
508
+
509
+ attributeChangedCallback() {
510
+ if (!this._canvas) return;
511
+ this._applyDesignSize();
512
+ this._fit();
513
+ this._scaleThumbs();
514
+ this._syncPrintPageRule();
515
+ }
516
+
517
+ // ── Rendering ─────────────────────────────────────────────────────
518
+
519
+ _render() {
520
+ const style = document.createElement("style");
521
+ style.textContent = stylesheet;
522
+
523
+ const stage = document.createElement("div");
524
+ stage.className = "stage";
525
+ const canvas = document.createElement("div");
526
+ canvas.className = "canvas";
527
+ const slot = document.createElement("slot");
528
+ slot.addEventListener("slotchange", () => this._onSlotChange());
529
+ canvas.appendChild(slot);
530
+ stage.appendChild(canvas);
531
+
532
+ const overlay = document.createElement("div");
533
+ overlay.className = "overlay";
534
+ overlay.setAttribute("role", "toolbar");
535
+ overlay.setAttribute("aria-label", "Deck controls");
536
+ overlay.innerHTML = `
537
+ <button class="btn prev" type="button" aria-label="Previous slide" title="Previous (←)">
538
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3L5 8l5 5"/></svg>
539
+ </button>
540
+ <span class="count" aria-live="polite"><span class="current">1</span><span class="total"> / 1</span></span>
541
+ <button class="btn next" type="button" aria-label="Next slide" title="Next (→)">
542
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
543
+ </button>
544
+ `;
545
+ overlay
546
+ .querySelector(".prev")
547
+ .addEventListener("click", () => this._advance(-1));
548
+ overlay
549
+ .querySelector(".next")
550
+ .addEventListener("click", () => this._advance(1));
551
+
552
+ const rail = document.createElement("div");
553
+ rail.className = "rail";
554
+
555
+ const resize = document.createElement("div");
556
+ resize.className = "rail-resize";
557
+ resize.addEventListener("pointerdown", (e) => {
558
+ e.preventDefault();
559
+ resize.setPointerCapture(e.pointerId);
560
+ resize.setAttribute("data-dragging", "");
561
+ const move = (ev) => this._setRailWidth(ev.clientX);
562
+ const up = () => {
563
+ resize.removeEventListener("pointermove", move);
564
+ resize.removeEventListener("pointerup", up);
565
+ resize.removeEventListener("pointercancel", up);
566
+ resize.removeAttribute("data-dragging");
567
+ try {
568
+ localStorage.setItem("deck-viewer.railWidth", String(this._railPx));
569
+ } catch {
570
+ /* opaque origin */
571
+ }
572
+ };
573
+ resize.addEventListener("pointermove", move);
574
+ resize.addEventListener("pointerup", up);
575
+ resize.addEventListener("pointercancel", up);
576
+ });
577
+
578
+ const menu = document.createElement("div");
579
+ menu.className = "ctxmenu";
580
+ menu.innerHTML = `
581
+ <button type="button" data-act="skip">Skip slide</button>
582
+ <button type="button" data-act="up">Move up</button>
583
+ <button type="button" data-act="down">Move down</button>
584
+ <button type="button" data-act="duplicate">Duplicate slide</button>
585
+ <hr>
586
+ <button type="button" data-act="delete">Delete slide</button>
587
+ `;
588
+ menu.addEventListener("click", (e) => {
589
+ const act =
590
+ e.target instanceof Element && e.target.getAttribute("data-act");
591
+ if (!act) return;
592
+ const i = this._menuIndex;
593
+ this._closeMenu();
594
+ if (act === "skip") this._toggleSkip(i);
595
+ else if (act === "up") this._moveSlide(i, i - 1);
596
+ else if (act === "down") this._moveSlide(i, i + 1);
597
+ else if (act === "duplicate") this._duplicateSlide(i);
598
+ else if (act === "delete") this._openConfirm(i);
599
+ });
600
+ menu.addEventListener("contextmenu", (e) => e.preventDefault());
601
+
602
+ const confirm = document.createElement("div");
603
+ confirm.className = "confirm-backdrop";
604
+ confirm.innerHTML = `
605
+ <div class="confirm" role="dialog" aria-modal="true">
606
+ <div class="body">
607
+ <div class="title">Delete slide?</div>
608
+ <div class="msg">This slide will be removed from the deck.</div>
609
+ </div>
610
+ <div class="footer">
611
+ <button type="button" class="cancel">Cancel</button>
612
+ <button type="button" class="danger">Delete</button>
613
+ </div>
614
+ </div>
615
+ `;
616
+ confirm.addEventListener("click", (e) => {
617
+ if (e.target === confirm) this._closeConfirm();
618
+ });
619
+ confirm
620
+ .querySelector(".cancel")
621
+ .addEventListener("click", () => this._closeConfirm());
622
+ confirm.querySelector(".danger").addEventListener("click", () => {
623
+ const i = this._confirmIndex;
624
+ this._closeConfirm();
625
+ this._deleteSlide(i);
626
+ });
627
+
628
+ const toast = document.createElement("div");
629
+ toast.className = "toast";
630
+
631
+ this._root.append(
632
+ style,
633
+ rail,
634
+ resize,
635
+ stage,
636
+ overlay,
637
+ menu,
638
+ confirm,
639
+ toast,
640
+ );
641
+ this._canvas = canvas;
642
+ this._stage = stage;
643
+ this._overlay = overlay;
644
+ this._rail = rail;
645
+ this._menu = menu;
646
+ this._confirm = confirm;
647
+ this._toast = toast;
648
+ this._countEl = overlay.querySelector(".current");
649
+ this._totalEl = overlay.querySelector(".total");
650
+
651
+ this._applyDesignSize();
652
+ let rw = RAIL_DEFAULT_W;
653
+ try {
654
+ const s = localStorage.getItem("deck-viewer.railWidth");
655
+ if (s) rw = parseInt(s, 10) || rw;
656
+ } catch {
657
+ /* opaque origin */
658
+ }
659
+ this._setRailWidth(rw);
660
+ }
661
+
662
+ _applyDesignSize() {
663
+ this._canvas.style.width = `${this.designWidth}px`;
664
+ this._canvas.style.height = `${this.designHeight}px`;
665
+ this._canvas.style.setProperty(
666
+ "--deck-design-w",
667
+ `${this.designWidth}px`,
668
+ );
669
+ this._canvas.style.setProperty(
670
+ "--deck-design-h",
671
+ `${this.designHeight}px`,
672
+ );
673
+ if (this._rail) {
674
+ this._rail.style.setProperty(
675
+ "--deck-aspect",
676
+ `${this.designWidth} / ${this.designHeight}`,
677
+ );
678
+ }
679
+ }
680
+
681
+ _setRailWidth(px) {
682
+ const w = Math.max(110, Math.min(340, Math.round(px)));
683
+ this._railPx = w;
684
+ this.style.setProperty("--deck-rail-w", `${w}px`);
685
+ this._fit();
686
+ if (!this._scaleRaf) {
687
+ this._scaleRaf = requestAnimationFrame(() => {
688
+ this._scaleRaf = null;
689
+ this._scaleThumbs();
690
+ });
691
+ }
692
+ }
693
+
694
+ /** @page must live in the document stylesheet — it's a no-op inside
695
+ * shadow DOM. One injected <head> style tag makes Print → Save as PDF
696
+ * yield one slide per page at design size with no margins. */
697
+ _syncPrintPageRule() {
698
+ const id = "deck-viewer-print-page";
699
+ let tag = document.getElementById(id);
700
+ if (!tag) {
701
+ tag = document.createElement("style");
702
+ tag.id = id;
703
+ document.head.appendChild(tag);
704
+ }
705
+ tag.textContent =
706
+ `@page { size: ${this.designWidth}px ${this.designHeight}px; margin: 0; } ` +
707
+ "@media print { html, body { margin: 0 !important; padding: 0 !important; " +
708
+ "background: none !important; overflow: visible !important; height: auto !important; } " +
709
+ "* { -webkit-print-color-adjust: exact; print-color-adjust: exact; } " +
710
+ // Jump animations/transitions to their end state so print never
711
+ // captures a mid-entrance frame — pairs with the beforeprint
712
+ // handler that marks every slide active.
713
+ "*, *::before, *::after { animation-delay: -99s !important; " +
714
+ "animation-duration: 0.001s !important; animation-iteration-count: 1 !important; " +
715
+ "animation-fill-mode: both !important; transition-duration: 0s !important; } }";
716
+ }
717
+
718
+ // ── Slide collection & navigation ─────────────────────────────────
719
+
720
+ _onSlotChange() {
721
+ this._collectSlides();
722
+ // Deep-link hash applies only on the initial collection — later
723
+ // slotchanges come from structural edits that already adjusted
724
+ // _index, and the stale hash must not override them.
725
+ if (!this._readySent) this._applyHash({ initial: true });
726
+ this._applyIndex({ showOverlay: false });
727
+ this._fit();
728
+ if (!this._readySent) {
729
+ this._readySent = true;
730
+ this._post({
731
+ type: "ready",
732
+ total: this._slides.length,
733
+ design: { width: this.designWidth, height: this.designHeight },
734
+ });
735
+ }
736
+ }
737
+
738
+ _collectSlides() {
739
+ const assigned = this._root
740
+ .querySelector("slot")
741
+ .assignedElements({ flatten: true });
742
+ this._slides = assigned.filter(
743
+ (el) => !/^(TEMPLATE|SCRIPT|STYLE)$/.test(el.tagName),
744
+ );
745
+ if (this._index >= this._slides.length) {
746
+ this._index = Math.max(0, this._slides.length - 1);
747
+ }
748
+ if (this._totalEl)
749
+ this._totalEl.textContent = ` / ${this._slides.length || 1}`;
750
+ this._markLastVisible();
751
+ this._renderRail();
752
+ this._syncEditable();
753
+ }
754
+
755
+ _markLastVisible() {
756
+ let last = null;
757
+ for (const s of this._slides) {
758
+ s.removeAttribute("data-deck-last-visible");
759
+ if (!s.hasAttribute("data-deck-skip")) last = s;
760
+ }
761
+ if (last) last.setAttribute("data-deck-last-visible", "");
762
+ }
763
+
764
+ /** Hash grammar: comma/&-separated tokens — a 1-based number is the
765
+ * slide deep-link, `rail` opens the thumbnail rail, `print` triggers
766
+ * the PDF-export auto-print. Examples: #3, #rail, #3,rail, #print. */
767
+ _parseHash() {
768
+ const tokens = (location.hash || "")
769
+ .replace(/^#/, "")
770
+ .split(/[,&]/)
771
+ .filter(Boolean);
772
+ const out = { slide: null, rail: false, print: false };
773
+ for (const t of tokens) {
774
+ if (/^\d+$/.test(t)) out.slide = parseInt(t, 10) - 1;
775
+ else if (t === "rail") out.rail = true;
776
+ else if (t === "print") out.print = true;
777
+ }
778
+ return out;
779
+ }
780
+
781
+ /** Mirror slide + rail state into the hash so a copied/reloaded URL
782
+ * restores the view. replaceState (no history entries, no hashchange
783
+ * loop) — throws in the opaque-origin preview iframe, where the
784
+ * host owns the state instead; safe to skip. */
785
+ _writeHash() {
786
+ const tokens = [String(this._index + 1)];
787
+ if (this._railOpen) tokens.push("rail");
788
+ try {
789
+ history.replaceState(null, "", `#${tokens.join(",")}`);
790
+ } catch {
791
+ /* sandboxed iframe */
792
+ }
793
+ }
794
+
795
+ _applyHash({ initial = false } = {}) {
796
+ const h = this._parseHash();
797
+ if (h.slide !== null && h.slide >= 0 && h.slide < this._slides.length) {
798
+ this._index = h.slide;
799
+ }
800
+ this._setRailOpen(h.rail, { writeHash: false });
801
+ if (!initial) this._applyIndex({ showOverlay: false });
802
+ if (h.print) this._maybeAutoPrint();
803
+ }
804
+
805
+ _setRailOpen(open, { writeHash = true } = {}) {
806
+ if (this._railOpen === open) return;
807
+ this._railOpen = open;
808
+ if (open) this.setAttribute("data-rail-open", "");
809
+ else this.removeAttribute("data-rail-open");
810
+ this._fit();
811
+ if (open) requestAnimationFrame(() => this._scaleThumbs());
812
+ if (writeHash) this._writeHash();
813
+ }
814
+
815
+ _applyIndex({ showOverlay = true } = {}) {
816
+ if (!this._slides.length) return;
817
+ const curr = this._index;
818
+ this._slides.forEach((s, i) => {
819
+ if (i === curr) s.setAttribute("data-deck-active", "");
820
+ else s.removeAttribute("data-deck-active");
821
+ });
822
+ if (this._countEl) this._countEl.textContent = String(curr + 1);
823
+ this._thumbs.forEach((t, i) => {
824
+ if (i === curr) {
825
+ t.thumb.setAttribute("data-current", "");
826
+ t.thumb.scrollIntoView({ block: "nearest" });
827
+ } else {
828
+ t.thumb.removeAttribute("data-current");
829
+ }
830
+ });
831
+ this._post({
832
+ type: "state",
833
+ index: curr,
834
+ total: this._slides.length,
835
+ skipped: this._skippedIndices(),
836
+ });
837
+ if (showOverlay) this._flashOverlay();
838
+ }
839
+
840
+ _skippedIndices() {
841
+ const out = [];
842
+ this._slides.forEach((s, i) => {
843
+ if (s.hasAttribute("data-deck-skip")) out.push(i);
844
+ });
845
+ return out;
846
+ }
847
+
848
+ _go(i, { showOverlay = true } = {}) {
849
+ if (!this._slides.length) return;
850
+ const clamped = Math.max(0, Math.min(this._slides.length - 1, i));
851
+ if (clamped === this._index) {
852
+ if (showOverlay) this._flashOverlay();
853
+ return;
854
+ }
855
+ this._index = clamped;
856
+ this._applyIndex({ showOverlay });
857
+ this._writeHash();
858
+ }
859
+
860
+ /** Step forward/back skipping data-deck-skip slides. */
861
+ _advance(dir) {
862
+ if (!this._slides.length) return;
863
+ let i = this._index + dir;
864
+ while (
865
+ i >= 0 &&
866
+ i < this._slides.length &&
867
+ this._slides[i].hasAttribute("data-deck-skip")
868
+ ) {
869
+ i += dir;
870
+ }
871
+ if (i < 0 || i >= this._slides.length) {
872
+ this._flashOverlay();
873
+ return;
874
+ }
875
+ this._go(i);
876
+ }
877
+
878
+ _flashOverlay() {
879
+ if (!this._overlay) return;
880
+ this._overlay.setAttribute("data-visible", "");
881
+ if (this._hideTimer) clearTimeout(this._hideTimer);
882
+ this._hideTimer = setTimeout(() => {
883
+ this._overlay.removeAttribute("data-visible");
884
+ }, OVERLAY_HIDE_MS);
885
+ }
886
+
887
+ _onMouseMove() {
888
+ this._flashOverlay();
889
+ }
890
+
891
+ _onResize() {
892
+ this._fit();
893
+ if (!this._scaleRaf) {
894
+ this._scaleRaf = requestAnimationFrame(() => {
895
+ this._scaleRaf = null;
896
+ this._scaleThumbs();
897
+ });
898
+ }
899
+ }
900
+
901
+ _railWidth() {
902
+ if (
903
+ !this._railOpen ||
904
+ this.hasAttribute("no-rail") ||
905
+ NARROW_MQ.matches
906
+ ) {
907
+ return 0;
908
+ }
909
+ return this._railPx || 0;
910
+ }
911
+
912
+ _fit() {
913
+ if (!this._canvas) return;
914
+ const rw = this._railWidth();
915
+ if (this._stage) this._stage.style.left = `${rw}px`;
916
+ if (this._overlay) this._overlay.style.marginLeft = `${rw / 2}px`;
917
+ const vw = window.innerWidth - rw;
918
+ const vh = window.innerHeight;
919
+ const s = Math.min(vw / this.designWidth, vh / this.designHeight);
920
+ this._canvas.style.transform = `scale(${s})`;
921
+ }
922
+
923
+ _onKey(e) {
924
+ const t = e.target;
925
+ // Don't steal keys while the user is typing (edit mode / inputs).
926
+ if (
927
+ t &&
928
+ (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))
929
+ )
930
+ return;
931
+ if (this._editingSlide) return;
932
+ if (this._confirm.hasAttribute("data-open")) {
933
+ if (e.key === "Escape") {
934
+ this._closeConfirm();
935
+ e.preventDefault();
936
+ }
937
+ return;
938
+ }
939
+ if (e.key === "Escape" && this._menu.hasAttribute("data-open")) {
940
+ this._closeMenu();
941
+ e.preventDefault();
942
+ return;
943
+ }
944
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
945
+
946
+ const key = e.key;
947
+ let handled = true;
948
+ if (key === "ArrowRight" || key === "PageDown" || key === " ")
949
+ this._advance(1);
950
+ else if (key === "ArrowLeft" || key === "PageUp") this._advance(-1);
951
+ else if (key === "Home") this._go(0);
952
+ else if (key === "End") this._go(this._slides.length - 1);
953
+ else if (key === "r" || key === "R") this._go(0);
954
+ else if (/^[0-9]$/.test(key)) {
955
+ const n = key === "0" ? 9 : parseInt(key, 10) - 1;
956
+ if (n < this._slides.length) this._go(n);
957
+ else handled = false;
958
+ } else handled = false;
959
+
960
+ if (handled) {
961
+ e.preventDefault();
962
+ this._flashOverlay();
963
+ }
964
+ }
965
+
966
+ _onTap(e) {
967
+ // Touch-only — keyboard + overlay cover desktop nav. Never navigate
968
+ // in edit mode (taps select text instead).
969
+ if (FINE_POINTER_MQ.matches || this._editMode) return;
970
+ const path = e.composedPath();
971
+ if (!this._stage || !path.includes(this._stage)) return;
972
+ if (e.defaultPrevented) return;
973
+ for (const n of path) {
974
+ if (n === this._stage) break;
975
+ if (n instanceof Element && n.matches(INTERACTIVE_SEL)) return;
976
+ }
977
+ e.preventDefault();
978
+ const rw = this._railWidth();
979
+ const mid = rw + (window.innerWidth - rw) / 2;
980
+ this._advance(e.clientX < mid ? -1 : 1);
981
+ }
982
+
983
+ // ── Print / PDF export ────────────────────────────────────────────
984
+
985
+ _maybeAutoPrint() {
986
+ if (!this._parseHash().print || this._printed) return;
987
+ this._printed = true;
988
+ // Wait for webfonts so the PDF gets the deck's real typography;
989
+ // capped so a broken font URL can't block export forever.
990
+ const go = () => setTimeout(() => window.print(), 50);
991
+ Promise.race([
992
+ document.fonts ? document.fonts.ready : Promise.resolve(),
993
+ new Promise((r) => setTimeout(r, 2000)),
994
+ ]).then(go, go);
995
+ }
996
+
997
+ _onHashChange() {
998
+ if (this._parseHash().print) this._printed = false;
999
+ this._applyHash();
1000
+ }
1001
+
1002
+ _onBeforePrint() {
1003
+ // Print lays every slide out as its own page, so [data-deck-active]-
1004
+ // gated entrance styles need the attribute on every slide.
1005
+ this._slides.forEach((s) => s.setAttribute("data-deck-active", ""));
1006
+ }
1007
+
1008
+ _onAfterPrint() {
1009
+ this._applyIndex({ showOverlay: false });
1010
+ }
1011
+
1012
+ // ── postMessage protocol ──────────────────────────────────────────
1013
+
1014
+ _post(msg) {
1015
+ if (window.parent === window) return;
1016
+ try {
1017
+ window.parent.postMessage(
1018
+ { v: PROTOCOL_V, source: "deck-viewer", ...msg },
1019
+ "*",
1020
+ );
1021
+ } catch {
1022
+ /* parent gone */
1023
+ }
1024
+ }
1025
+
1026
+ _onMessage(e) {
1027
+ const d = e.data;
1028
+ if (!d || d.v !== PROTOCOL_V || typeof d.type !== "string") return;
1029
+ if (d.type === "ack" && typeof d.opId === "string") {
1030
+ const pending = this._pendingAcks.get(d.opId);
1031
+ if (!pending) return;
1032
+ clearTimeout(pending.timer);
1033
+ this._pendingAcks.delete(d.opId);
1034
+ if (pending.structural) this._railLocked = this._hasPendingStructural();
1035
+ if (d.ok === false) {
1036
+ this._showToast("Couldn't save the change — reload the preview.");
1037
+ }
1038
+ } else if (d.type === "set-edit-mode") {
1039
+ this._setEditMode(Boolean(d.enabled));
1040
+ } else if (d.type === "set-rail") {
1041
+ this._setRailOpen(Boolean(d.open));
1042
+ } else if (d.type === "goto" && typeof d.index === "number") {
1043
+ this._go(d.index, { showOverlay: false });
1044
+ } else if (d.type === "print") {
1045
+ window.print();
1046
+ }
1047
+ }
1048
+
1049
+ _hasPendingStructural() {
1050
+ for (const p of this._pendingAcks.values()) {
1051
+ if (p.structural) return true;
1052
+ }
1053
+ return false;
1054
+ }
1055
+
1056
+ /** Emit an op to the parent. Structural ops lock the rail until the
1057
+ * ack lands so a rapid second click can't address stale indices. */
1058
+ _emitOp(op, { structural = false } = {}) {
1059
+ const opId = `op-${++this._opSeq}`;
1060
+ if (window.parent !== window) {
1061
+ const timer = setTimeout(() => {
1062
+ this._pendingAcks.delete(opId);
1063
+ this._railLocked = this._hasPendingStructural();
1064
+ this._showToast("Save timed out — reload the preview.");
1065
+ }, ACK_TIMEOUT_MS);
1066
+ this._pendingAcks.set(opId, { timer, structural });
1067
+ if (structural) this._railLocked = true;
1068
+ }
1069
+ this._post({
1070
+ type: "op",
1071
+ opId,
1072
+ witness: { childCount: this._slides.length },
1073
+ op,
1074
+ });
1075
+ }
1076
+
1077
+ _showToast(text) {
1078
+ if (!this._toast) return;
1079
+ this._toast.textContent = text;
1080
+ this._toast.setAttribute("data-visible", "");
1081
+ clearTimeout(this._toastTimer);
1082
+ this._toastTimer = setTimeout(() => {
1083
+ this._toast.removeAttribute("data-visible");
1084
+ }, 4000);
1085
+ }
1086
+
1087
+ // ── Edit mode ─────────────────────────────────────────────────────
1088
+
1089
+ _setEditMode(enabled) {
1090
+ if (this._editMode === enabled) return;
1091
+ this._editMode = enabled;
1092
+ if (enabled) this.setAttribute("data-edit-mode", "");
1093
+ else this.removeAttribute("data-edit-mode");
1094
+ // Structural ops (reorder/duplicate/delete/skip) live in the rail —
1095
+ // editing without it would be a dead end.
1096
+ if (enabled) this._setRailOpen(true);
1097
+ this._syncEditable();
1098
+ if (!enabled) {
1099
+ this._flushTextEdit();
1100
+ this._closeMenu();
1101
+ this._closeConfirm();
1102
+ }
1103
+ }
1104
+
1105
+ _syncEditable() {
1106
+ for (const s of this._slides) {
1107
+ if (this._editMode) {
1108
+ s.setAttribute("contenteditable", "true");
1109
+ s.setAttribute("spellcheck", "false");
1110
+ } else {
1111
+ s.removeAttribute("contenteditable");
1112
+ s.removeAttribute("spellcheck");
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ _onFocusIn(e) {
1118
+ if (!this._editMode) return;
1119
+ const slide = this._slideFromNode(e.target);
1120
+ if (!slide) return;
1121
+ if (this._editingSlide && this._editingSlide !== slide)
1122
+ this._flushTextEdit();
1123
+ this._editingSlide = slide;
1124
+ this._editingSnapshot = slide.innerHTML;
1125
+ }
1126
+
1127
+ _onFocusOut() {
1128
+ if (!this._editingSlide) return;
1129
+ // focusout fires before the new focus target settles; defer so a
1130
+ // focus move WITHIN the same slide doesn't flush.
1131
+ setTimeout(() => {
1132
+ const active = document.activeElement;
1133
+ if (active && this._slideFromNode(active) === this._editingSlide)
1134
+ return;
1135
+ this._flushTextEdit();
1136
+ }, 0);
1137
+ }
1138
+
1139
+ _onInput(e) {
1140
+ if (!this._editMode) return;
1141
+ const slide = this._slideFromNode(e.target);
1142
+ if (!slide || slide !== this._editingSlide) return;
1143
+ clearTimeout(this._textDebounce);
1144
+ this._textDebounce = setTimeout(
1145
+ () => this._flushTextEdit({ keepFocus: true }),
1146
+ TEXT_EDIT_DEBOUNCE_MS,
1147
+ );
1148
+ // Live thumbnail update is cheap enough on the debounce flush only.
1149
+ }
1150
+
1151
+ _slideFromNode(node) {
1152
+ let n = node;
1153
+ while (n && n.parentElement !== this) n = n.parentElement;
1154
+ return n && this._slides.includes(n) ? n : null;
1155
+ }
1156
+
1157
+ /** Post the edited slide's cleaned HTML if it changed. */
1158
+ _flushTextEdit({ keepFocus = false } = {}) {
1159
+ clearTimeout(this._textDebounce);
1160
+ const slide = this._editingSlide;
1161
+ if (!slide) return;
1162
+ if (!keepFocus) {
1163
+ this._editingSlide = null;
1164
+ }
1165
+ const snapshot = this._editingSnapshot;
1166
+ if (slide.innerHTML === snapshot) return;
1167
+ this._editingSnapshot = slide.innerHTML;
1168
+ const at = this._slides.indexOf(slide);
1169
+ if (at < 0) return;
1170
+ this._emitOp({ kind: "replace", at, html: this._cleanSlideHtml(slide) });
1171
+ this._refreshThumb(at);
1172
+ }
1173
+
1174
+ /** Clone + strip runtime-managed attributes so persisted HTML is clean. */
1175
+ _cleanSlideHtml(slide) {
1176
+ const clone = slide.cloneNode(true);
1177
+ for (const attr of RUNTIME_ATTRS) clone.removeAttribute(attr);
1178
+ return clone.outerHTML;
1179
+ }
1180
+
1181
+ // ── Structural ops (self-apply + emit) ────────────────────────────
1182
+
1183
+ _deleteSlide(i) {
1184
+ if (this._railLocked) return;
1185
+ const slide = this._slides[i];
1186
+ if (!slide || this._slides.length <= 1) return;
1187
+ const cur = this._index;
1188
+ this._index =
1189
+ i < cur || (i === cur && i === this._slides.length - 1) ? cur - 1 : cur;
1190
+ this._emitOp({ kind: "remove", at: i }, { structural: true });
1191
+ slide.remove(); // slotchange re-collects + re-renders the rail
1192
+ }
1193
+
1194
+ _duplicateSlide(i) {
1195
+ if (this._railLocked) return;
1196
+ const slide = this._slides[i];
1197
+ if (!slide) return;
1198
+ this._emitOp({ kind: "duplicate", at: i }, { structural: true });
1199
+ const copy = slide.cloneNode(true);
1200
+ for (const attr of RUNTIME_ATTRS) copy.removeAttribute(attr);
1201
+ copy.removeAttribute("id");
1202
+ copy.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id"));
1203
+ this._index = i + 1;
1204
+ this.insertBefore(copy, slide.nextSibling);
1205
+ }
1206
+
1207
+ _moveSlide(i, j) {
1208
+ if (this._railLocked || j < 0 || j >= this._slides.length || j === i)
1209
+ return;
1210
+ const cur = this._index;
1211
+ this._index =
1212
+ cur === i
1213
+ ? j
1214
+ : i < cur && j >= cur
1215
+ ? cur - 1
1216
+ : i > cur && j <= cur
1217
+ ? cur + 1
1218
+ : cur;
1219
+ const slide = this._slides[i];
1220
+ this._emitOp({ kind: "move", from: i, to: j }, { structural: true });
1221
+ const ref = j < i ? this._slides[j] : this._slides[j].nextSibling;
1222
+ this.insertBefore(slide, ref);
1223
+ }
1224
+
1225
+ _toggleSkip(i) {
1226
+ if (this._railLocked) return;
1227
+ const slide = this._slides[i];
1228
+ if (!slide) return;
1229
+ const on = !slide.hasAttribute("data-deck-skip");
1230
+ this._emitOp(
1231
+ on
1232
+ ? { kind: "set-attr", at: i, name: "data-deck-skip", value: "" }
1233
+ : { kind: "remove-attr", at: i, name: "data-deck-skip" },
1234
+ { structural: true },
1235
+ );
1236
+ if (on) slide.setAttribute("data-deck-skip", "");
1237
+ else slide.removeAttribute("data-deck-skip");
1238
+ this._markLastVisible();
1239
+ const t = this._thumbs[i];
1240
+ if (t) {
1241
+ if (on) t.thumb.setAttribute("data-skip", "");
1242
+ else t.thumb.removeAttribute("data-skip");
1243
+ }
1244
+ this._post({
1245
+ type: "state",
1246
+ index: this._index,
1247
+ total: this._slides.length,
1248
+ skipped: this._skippedIndices(),
1249
+ });
1250
+ }
1251
+
1252
+ // ── Thumbnail rail ────────────────────────────────────────────────
1253
+ //
1254
+ // Thumbs are static deep clones rendered inside a nested shadow root
1255
+ // per frame. Custom properties inherit across shadow boundaries, and
1256
+ // deck templates are inline-style-first, so cloning + a snapshot of
1257
+ // the document's stylesheets covers authored styling without the
1258
+ // :root-rewriting machinery a fully general solution would need.
1259
+
1260
+ _authorSheet() {
1261
+ if (this._sheet !== undefined) return this._sheet;
1262
+ const css = Array.from(document.styleSheets)
1263
+ .map((sh) => {
1264
+ try {
1265
+ return Array.from(sh.cssRules)
1266
+ .map((r) => r.cssText)
1267
+ .join("\n");
1268
+ } catch {
1269
+ return ""; // cross-origin sheet
1270
+ }
1271
+ })
1272
+ .join("\n");
1273
+ try {
1274
+ this._sheet = new CSSStyleSheet();
1275
+ this._sheet.replaceSync(css);
1276
+ } catch {
1277
+ this._sheet = null;
1278
+ this._sheetCss = css;
1279
+ }
1280
+ return this._sheet;
1281
+ }
1282
+
1283
+ _renderRail() {
1284
+ if (!this._rail) return;
1285
+ const st = this._rail.scrollTop;
1286
+ // Reconcile: reuse thumbs keyed by slide element.
1287
+ const bySlide = new Map();
1288
+ for (const t of this._thumbs) bySlide.set(t.slide, t);
1289
+ const next = [];
1290
+ for (const slide of this._slides) {
1291
+ let t = bySlide.get(slide);
1292
+ if (t) bySlide.delete(slide);
1293
+ else t = this._makeThumb(slide);
1294
+ next.push(t);
1295
+ }
1296
+ bySlide.forEach((t) => t.thumb.remove());
1297
+ next.forEach((t, i) => {
1298
+ const at = this._rail.children[i];
1299
+ if (at !== t.thumb) this._rail.insertBefore(t.thumb, at || null);
1300
+ t.i = i;
1301
+ t.num.textContent = String(i + 1);
1302
+ if (t.slide.hasAttribute("data-deck-skip"))
1303
+ t.thumb.setAttribute("data-skip", "");
1304
+ else t.thumb.removeAttribute("data-skip");
1305
+ });
1306
+ this._thumbs = next;
1307
+ this._rail.scrollTop = st;
1308
+ requestAnimationFrame(() => this._scaleThumbs());
1309
+ }
1310
+
1311
+ _makeThumb(slide) {
1312
+ const thumb = document.createElement("div");
1313
+ thumb.className = "thumb";
1314
+ thumb.tabIndex = 0;
1315
+ const num = document.createElement("div");
1316
+ num.className = "num";
1317
+ const frame = document.createElement("div");
1318
+ frame.className = "frame";
1319
+ thumb.append(num, frame);
1320
+
1321
+ const entry = { thumb, num, frame, slide, clone: null, i: -1 };
1322
+ const idx = () => entry.i;
1323
+
1324
+ thumb.addEventListener("click", () => this._go(idx()));
1325
+ thumb.addEventListener("keydown", (e) => {
1326
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
1327
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
1328
+ e.preventDefault();
1329
+ e.stopPropagation();
1330
+ this._go(idx() + (e.key === "ArrowDown" ? 1 : -1));
1331
+ const cur = this._thumbs[this._index];
1332
+ if (cur) cur.thumb.focus({ preventScroll: true });
1333
+ });
1334
+ thumb.addEventListener("contextmenu", (e) => {
1335
+ if (!this._editMode) return;
1336
+ e.preventDefault();
1337
+ this._openMenu(idx(), e.clientX, e.clientY);
1338
+ });
1339
+ thumb.draggable = true;
1340
+ thumb.addEventListener("dragstart", (e) => {
1341
+ if (!this._editMode || this._railLocked) {
1342
+ e.preventDefault();
1343
+ return;
1344
+ }
1345
+ this._dragFrom = idx();
1346
+ thumb.setAttribute("data-dragging", "");
1347
+ e.dataTransfer.effectAllowed = "move";
1348
+ try {
1349
+ e.dataTransfer.setData("text/plain", String(this._dragFrom));
1350
+ } catch {
1351
+ /* ignore */
1352
+ }
1353
+ });
1354
+ thumb.addEventListener("dragend", () => {
1355
+ thumb.removeAttribute("data-dragging");
1356
+ this._clearDrop();
1357
+ this._dragFrom = null;
1358
+ });
1359
+ thumb.addEventListener("dragover", (e) => {
1360
+ if (this._dragFrom == null) return;
1361
+ e.preventDefault();
1362
+ e.dataTransfer.dropEffect = "move";
1363
+ const r = thumb.getBoundingClientRect();
1364
+ this._setDrop(
1365
+ idx(),
1366
+ e.clientY < r.top + r.height / 2 ? "before" : "after",
1367
+ );
1368
+ });
1369
+ thumb.addEventListener("drop", (e) => {
1370
+ if (this._dragFrom == null) return;
1371
+ e.preventDefault();
1372
+ const i = idx();
1373
+ const r = thumb.getBoundingClientRect();
1374
+ let to = e.clientY >= r.top + r.height / 2 ? i + 1 : i;
1375
+ if (this._dragFrom < to) to--;
1376
+ const from = this._dragFrom;
1377
+ this._clearDrop();
1378
+ this._dragFrom = null;
1379
+ if (to !== from) this._moveSlide(from, to);
1380
+ });
1381
+
1382
+ this._materializeThumb(entry);
1383
+ return entry;
1384
+ }
1385
+
1386
+ _materializeThumb(entry) {
1387
+ const dw = this.designWidth;
1388
+ const dh = this.designHeight;
1389
+ const clone = entry.slide.cloneNode(true);
1390
+ for (const attr of RUNTIME_ATTRS) clone.removeAttribute(attr);
1391
+ clone.removeAttribute("id");
1392
+ clone.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id"));
1393
+ // Neuter heavy/stateful media in thumbnails.
1394
+ clone
1395
+ .querySelectorAll("iframe, audio, video, object, embed")
1396
+ .forEach((el) => {
1397
+ el.removeAttribute("src");
1398
+ el.removeAttribute("srcdoc");
1399
+ el.innerHTML = "";
1400
+ });
1401
+ clone.querySelectorAll("img").forEach((el) => {
1402
+ el.loading = "lazy";
1403
+ el.decoding = "async";
1404
+ });
1405
+ clone.style.cssText +=
1406
+ ";position:absolute;top:0;left:0;transform-origin:0 0;pointer-events:none;" +
1407
+ `width:${dw}px;height:${dh}px;box-sizing:border-box;overflow:hidden;` +
1408
+ "visibility:visible;opacity:1;";
1409
+ const host = document.createElement("div");
1410
+ host.style.cssText = "position:absolute;inset:0;";
1411
+ const sr = host.attachShadow({ mode: "open" });
1412
+ const sheet = this._authorSheet();
1413
+ if (sheet) {
1414
+ sr.adoptedStyleSheets = [sheet];
1415
+ } else if (this._sheetCss) {
1416
+ const st = document.createElement("style");
1417
+ st.textContent = this._sheetCss;
1418
+ sr.appendChild(st);
1419
+ }
1420
+ sr.appendChild(clone);
1421
+ entry.frame.textContent = "";
1422
+ entry.frame.appendChild(host);
1423
+ entry.clone = clone;
1424
+ if (this._thumbScale)
1425
+ clone.style.transform = `scale(${this._thumbScale})`;
1426
+ }
1427
+
1428
+ _refreshThumb(i) {
1429
+ const entry = this._thumbs[i];
1430
+ if (entry) this._materializeThumb(entry);
1431
+ }
1432
+
1433
+ _scaleThumbs() {
1434
+ if (!this._thumbs.length) return;
1435
+ const fw = this._thumbs[0].frame.offsetWidth;
1436
+ if (!fw) return;
1437
+ this._thumbScale = fw / this.designWidth;
1438
+ for (const { clone } of this._thumbs) {
1439
+ if (clone) clone.style.transform = `scale(${this._thumbScale})`;
1440
+ }
1441
+ }
1442
+
1443
+ _setDrop(i, where) {
1444
+ const t = this._thumbs[i];
1445
+ if (this._dropOn && this._dropOn !== t)
1446
+ this._dropOn.thumb.removeAttribute("data-drop");
1447
+ if (t) t.thumb.setAttribute("data-drop", where);
1448
+ this._dropOn = t || null;
1449
+ }
1450
+
1451
+ _clearDrop() {
1452
+ if (this._dropOn) this._dropOn.thumb.removeAttribute("data-drop");
1453
+ this._dropOn = null;
1454
+ }
1455
+
1456
+ _openMenu(i, x, y) {
1457
+ this._menuIndex = i;
1458
+ const slide = this._slides[i];
1459
+ const skip = slide && slide.hasAttribute("data-deck-skip");
1460
+ this._menu.querySelector('[data-act="skip"]').textContent = skip
1461
+ ? "Unskip slide"
1462
+ : "Skip slide";
1463
+ this._menu.querySelector('[data-act="up"]').disabled = i <= 0;
1464
+ this._menu.querySelector('[data-act="down"]').disabled =
1465
+ i >= this._slides.length - 1;
1466
+ this._menu.querySelector('[data-act="delete"]').disabled =
1467
+ this._slides.length <= 1;
1468
+ this._menu.style.left = `${x}px`;
1469
+ this._menu.style.top = `${y}px`;
1470
+ this._menu.setAttribute("data-open", "");
1471
+ const r = this._menu.getBoundingClientRect();
1472
+ this._menu.style.left = `${Math.max(4, Math.min(x, window.innerWidth - r.width - 4))}px`;
1473
+ this._menu.style.top = `${Math.max(4, Math.min(y, window.innerHeight - r.height - 4))}px`;
1474
+ }
1475
+
1476
+ _closeMenu() {
1477
+ this._menu.removeAttribute("data-open");
1478
+ this._menuIndex = -1;
1479
+ }
1480
+
1481
+ _openConfirm(i) {
1482
+ this._confirmIndex = i;
1483
+ this._confirm.querySelector(".title").textContent =
1484
+ `Delete slide ${i + 1}?`;
1485
+ this._confirm.setAttribute("data-open", "");
1486
+ this._confirm.querySelector(".danger").focus();
1487
+ }
1488
+
1489
+ _closeConfirm() {
1490
+ this._confirm.removeAttribute("data-open");
1491
+ this._confirmIndex = -1;
1492
+ }
1493
+
1494
+ // ── Public API ────────────────────────────────────────────────────
1495
+
1496
+ get index() {
1497
+ return this._index;
1498
+ }
1499
+ get length() {
1500
+ return this._slides.length;
1501
+ }
1502
+ goTo(i) {
1503
+ this._go(i);
1504
+ }
1505
+ next() {
1506
+ this._advance(1);
1507
+ }
1508
+ prev() {
1509
+ this._advance(-1);
1510
+ }
1511
+ }
1512
+
1513
+ if (!customElements.get("deck-viewer")) {
1514
+ customElements.define("deck-viewer", DeckViewer);
1515
+ }
1516
+ })();