@parhelia/core 0.1.12570 → 0.1.12572

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 (584) hide show
  1. package/dist/agents-view/AgentCard.d.ts +6 -4
  2. package/dist/agents-view/AgentCard.js +143 -24
  3. package/dist/agents-view/AgentCard.js.map +1 -1
  4. package/dist/agents-view/AgentsInbox.d.ts +1 -1
  5. package/dist/agents-view/AgentsInbox.js +7 -92
  6. package/dist/agents-view/AgentsInbox.js.map +1 -1
  7. package/dist/agents-view/AgentsTitlebar.js +3 -2
  8. package/dist/agents-view/AgentsTitlebar.js.map +1 -1
  9. package/dist/agents-view/AgentsView.d.ts +6 -7
  10. package/dist/agents-view/AgentsView.js +191 -99
  11. package/dist/agents-view/AgentsView.js.map +1 -1
  12. package/dist/agents-view/AgentsWorkspaceView.d.ts +2 -6
  13. package/dist/agents-view/AgentsWorkspaceView.js +266 -113
  14. package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
  15. package/dist/agents-view/ProfileAgentsGroup.d.ts +2 -1
  16. package/dist/agents-view/ProfileAgentsGroup.js +4 -3
  17. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
  18. package/dist/components/ActionButton.d.ts +1 -1
  19. package/dist/components/ActionButton.js.map +1 -1
  20. package/dist/components/FilterInput.d.ts +1 -1
  21. package/dist/components/FilterInput.js +1 -1
  22. package/dist/components/FilterInput.js.map +1 -1
  23. package/dist/components/ui/LanguageSelector.js +2 -4
  24. package/dist/components/ui/LanguageSelector.js.map +1 -1
  25. package/dist/components/ui/PlaceholderInput.js +3 -3
  26. package/dist/components/ui/PlaceholderInput.js.map +1 -1
  27. package/dist/components/ui/PlaceholderInputTypes.js +1 -1
  28. package/dist/components/ui/PlaceholderInputTypes.js.map +1 -1
  29. package/dist/components/ui/alert-dialog.d.ts +1 -1
  30. package/dist/components/ui/alert-dialog.js +6 -10
  31. package/dist/components/ui/alert-dialog.js.map +1 -1
  32. package/dist/components/ui/button.d.ts +4 -4
  33. package/dist/components/ui/button.js +4 -1
  34. package/dist/components/ui/button.js.map +1 -1
  35. package/dist/components/ui/context-menu.d.ts +1 -1
  36. package/dist/components/ui/context-menu.js +12 -4
  37. package/dist/components/ui/context-menu.js.map +1 -1
  38. package/dist/components/ui/copy-button.d.ts +2 -1
  39. package/dist/components/ui/copy-button.js +2 -2
  40. package/dist/components/ui/copy-button.js.map +1 -1
  41. package/dist/components/ui/dialog.d.ts +1 -1
  42. package/dist/components/ui/dialog.js +21 -126
  43. package/dist/components/ui/dialog.js.map +1 -1
  44. package/dist/components/ui/input.d.ts +1 -1
  45. package/dist/components/ui/input.js +5 -3
  46. package/dist/components/ui/input.js.map +1 -1
  47. package/dist/components/ui/paste-button.d.ts +2 -1
  48. package/dist/components/ui/paste-button.js +2 -2
  49. package/dist/components/ui/paste-button.js.map +1 -1
  50. package/dist/components/ui/popover.js +1 -9
  51. package/dist/components/ui/popover.js.map +1 -1
  52. package/dist/components/ui/select.js +1 -1
  53. package/dist/components/ui/select.js.map +1 -1
  54. package/dist/components/ui/styled-dialog-title.js +1 -1
  55. package/dist/components/ui/styled-dialog-title.js.map +1 -1
  56. package/dist/components/ui/tabs.d.ts +1 -1
  57. package/dist/components/ui/tabs.js +4 -11
  58. package/dist/components/ui/tabs.js.map +1 -1
  59. package/dist/config/config.d.ts +4 -2
  60. package/dist/config/config.js +250 -70
  61. package/dist/config/config.js.map +1 -1
  62. package/dist/config/types/workspace.d.ts +6 -0
  63. package/dist/config/types.d.ts +63 -12
  64. package/dist/config/types.js.map +1 -1
  65. package/dist/editor/ConfirmationDialog.js +20 -4
  66. package/dist/editor/ConfirmationDialog.js.map +1 -1
  67. package/dist/editor/ContentTree.d.ts +2 -1
  68. package/dist/editor/ContentTree.js +93 -32
  69. package/dist/editor/ContentTree.js.map +1 -1
  70. package/dist/editor/Editor.js +87 -22
  71. package/dist/editor/Editor.js.map +1 -1
  72. package/dist/editor/FieldHistory.js +84 -36
  73. package/dist/editor/FieldHistory.js.map +1 -1
  74. package/dist/editor/FieldListField.js +21 -9
  75. package/dist/editor/FieldListField.js.map +1 -1
  76. package/dist/editor/FieldListFieldWithFallbacks.js +23 -2
  77. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  78. package/dist/editor/GlobalMenuBar.js +29 -2
  79. package/dist/editor/GlobalMenuBar.js.map +1 -1
  80. package/dist/editor/ImageEditor.js +5 -2
  81. package/dist/editor/ImageEditor.js.map +1 -1
  82. package/dist/editor/ItemInfo.js +36 -1
  83. package/dist/editor/ItemInfo.js.map +1 -1
  84. package/dist/editor/LinkEditorDialog.js +3 -0
  85. package/dist/editor/LinkEditorDialog.js.map +1 -1
  86. package/dist/editor/MainLayout.d.ts +0 -2
  87. package/dist/editor/MainLayout.js +65 -8
  88. package/dist/editor/MainLayout.js.map +1 -1
  89. package/dist/editor/MigrationsView.js +29 -5
  90. package/dist/editor/MigrationsView.js.map +1 -1
  91. package/dist/editor/MobileLayout.js +37 -12
  92. package/dist/editor/MobileLayout.js.map +1 -1
  93. package/dist/editor/PictureCropper.js +54 -45
  94. package/dist/editor/PictureCropper.js.map +1 -1
  95. package/dist/editor/PictureEditor.js +17 -15
  96. package/dist/editor/PictureEditor.js.map +1 -1
  97. package/dist/editor/QuickItemSwitcher.js +21 -21
  98. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  99. package/dist/editor/SetupWizard.js +52 -12
  100. package/dist/editor/SetupWizard.js.map +1 -1
  101. package/dist/editor/Titlebar.js +7 -2
  102. package/dist/editor/Titlebar.js.map +1 -1
  103. package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
  104. package/dist/editor/ai/AgentCostDisplay.js +1 -1
  105. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  106. package/dist/editor/ai/AgentDocumentList.js +32 -14
  107. package/dist/editor/ai/AgentDocumentList.js.map +1 -1
  108. package/dist/editor/ai/AgentGreeting.js +3 -2
  109. package/dist/editor/ai/AgentGreeting.js.map +1 -1
  110. package/dist/editor/ai/AgentProfileSelector.js +2 -1
  111. package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
  112. package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
  113. package/dist/editor/ai/AgentStatusBadge.js +67 -65
  114. package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
  115. package/dist/editor/ai/AgentTerminal.d.ts +14 -2
  116. package/dist/editor/ai/AgentTerminal.js +2424 -496
  117. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  118. package/dist/editor/ai/AgentTerminalStatusBar.d.ts +8 -3
  119. package/dist/editor/ai/AgentTerminalStatusBar.js +481 -56
  120. package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
  121. package/dist/editor/ai/Agents.js +161 -113
  122. package/dist/editor/ai/Agents.js.map +1 -1
  123. package/dist/editor/ai/AiResponseMessage.d.ts +10 -1
  124. package/dist/editor/ai/AiResponseMessage.js +267 -26
  125. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  126. package/dist/editor/ai/ContextInfoBar.d.ts +2 -3
  127. package/dist/editor/ai/ContextInfoBar.js +64 -7
  128. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  129. package/dist/editor/ai/GuidanceOverlay.js +17 -11
  130. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  131. package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
  132. package/dist/editor/ai/InlineAiDialog.js +514 -192
  133. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  134. package/dist/editor/ai/InlineAiTrigger.js +115 -12
  135. package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
  136. package/dist/editor/ai/MediaImage.js +40 -8
  137. package/dist/editor/ai/MediaImage.js.map +1 -1
  138. package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
  139. package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
  140. package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
  141. package/dist/editor/ai/ToolCallDisplay.js +617 -202
  142. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  143. package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
  144. package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
  145. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  146. package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
  147. package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
  148. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  149. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +115 -0
  150. package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
  151. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  152. package/dist/editor/ai/types.d.ts +3 -1
  153. package/dist/editor/ai/useAgentStatus.d.ts +2 -1
  154. package/dist/editor/ai/useAgentStatus.js +90 -100
  155. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  156. package/dist/editor/ai/useInlineAiPosition.js +45 -5
  157. package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
  158. package/dist/editor/client/AboutDialog.js +4 -2
  159. package/dist/editor/client/AboutDialog.js.map +1 -1
  160. package/dist/editor/client/EditorShell.d.ts +4 -1
  161. package/dist/editor/client/EditorShell.js +837 -253
  162. package/dist/editor/client/EditorShell.js.map +1 -1
  163. package/dist/editor/client/editContext.d.ts +33 -19
  164. package/dist/editor/client/editContext.js.map +1 -1
  165. package/dist/editor/client/helpers.js +6 -0
  166. package/dist/editor/client/helpers.js.map +1 -1
  167. package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
  168. package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
  169. package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
  170. package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
  171. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  172. package/dist/editor/client/hooks/useQuota.d.ts +8 -0
  173. package/dist/editor/client/hooks/useQuota.js.map +1 -1
  174. package/dist/editor/client/hooks/useSocketMessageHandler.js +73 -15
  175. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  176. package/dist/editor/client/itemsRepository.js +10 -6
  177. package/dist/editor/client/itemsRepository.js.map +1 -1
  178. package/dist/editor/client/operations.d.ts +6 -3
  179. package/dist/editor/client/operations.js +208 -30
  180. package/dist/editor/client/operations.js.map +1 -1
  181. package/dist/editor/client/pageModelBuilder.js +4 -31
  182. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  183. package/dist/editor/client/ui/DevModeIndicator.js +2 -2
  184. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
  185. package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
  186. package/dist/editor/client/ui/EditorChrome.js +55 -72
  187. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  188. package/dist/editor/client/ui/FullscreenControls.js +5 -3
  189. package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
  190. package/dist/editor/commands/commands.d.ts +11 -1
  191. package/dist/editor/commands/commands.js +12 -1
  192. package/dist/editor/commands/commands.js.map +1 -1
  193. package/dist/editor/commands/componentCommands.js +109 -55
  194. package/dist/editor/commands/componentCommands.js.map +1 -1
  195. package/dist/editor/commands/customCommandConverter.d.ts +8 -1
  196. package/dist/editor/commands/customCommandConverter.js +35 -5
  197. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  198. package/dist/editor/commands/handlers/agentHandler.js +2 -1
  199. package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
  200. package/dist/editor/commands/itemCommands.d.ts +3 -0
  201. package/dist/editor/commands/itemCommands.js +93 -10
  202. package/dist/editor/commands/itemCommands.js.map +1 -1
  203. package/dist/editor/commands/undo.d.ts +9 -15
  204. package/dist/editor/commands/undo.js +24 -0
  205. package/dist/editor/commands/undo.js.map +1 -1
  206. package/dist/editor/context-menu/InsertMenu.js +83 -39
  207. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  208. package/dist/editor/field-types/MultiLineText.js +1 -1
  209. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  210. package/dist/editor/field-types/RawEditor.js +1 -1
  211. package/dist/editor/field-types/RichTextEditor.js +13 -5
  212. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  213. package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
  214. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  215. package/dist/editor/field-types/SingleLineText.js +1 -1
  216. package/dist/editor/field-types/TreeListEditor.js +3 -2
  217. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  218. package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
  219. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
  220. package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
  221. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  222. package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
  223. package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
  224. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
  225. package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
  226. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  227. package/dist/editor/field-types/richtext/types.d.ts +2 -0
  228. package/dist/editor/field-types/richtext/types.js.map +1 -1
  229. package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
  230. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
  231. package/dist/editor/field-types/textContextMenuFactory.js +3 -2
  232. package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
  233. package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
  234. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  235. package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
  236. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  237. package/dist/editor/media-selector/MediaSelector.js +7 -1
  238. package/dist/editor/media-selector/MediaSelector.js.map +1 -1
  239. package/dist/editor/media-selector/TreeSelector.js +40 -35
  240. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  241. package/dist/editor/menubar/ActiveUsers.js +1 -1
  242. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  243. package/dist/editor/menubar/GenericToolbar.js +4 -2
  244. package/dist/editor/menubar/GenericToolbar.js.map +1 -1
  245. package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
  246. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  247. package/dist/editor/menubar/PageSelector.js +26 -147
  248. package/dist/editor/menubar/PageSelector.js.map +1 -1
  249. package/dist/editor/menubar/Separator.js +1 -1
  250. package/dist/editor/menubar/VersionSelector.js +2 -4
  251. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  252. package/dist/editor/menubar/WorkflowButton.js +39 -12
  253. package/dist/editor/menubar/WorkflowButton.js.map +1 -1
  254. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
  255. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
  256. package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
  257. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  258. package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
  259. package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
  260. package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
  261. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
  262. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  263. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
  264. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  265. package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
  266. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  267. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  268. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  269. package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
  270. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  271. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
  272. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  273. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
  274. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  275. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
  276. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  277. package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
  278. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  279. package/dist/editor/page-viewer/EditorForm.js +69 -11
  280. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  281. package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
  282. package/dist/editor/page-viewer/MiniMap.js +91 -28
  283. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  284. package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
  285. package/dist/editor/page-viewer/PageViewer.js +92 -19
  286. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  287. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  288. package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
  289. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  290. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
  291. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  292. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  293. package/dist/editor/page-viewer/pageViewContext.js +51 -14
  294. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  295. package/dist/editor/pageModel.d.ts +14 -1
  296. package/dist/editor/reviews/Comment.js +26 -12
  297. package/dist/editor/reviews/Comment.js.map +1 -1
  298. package/dist/editor/reviews/CommentDisplayPopover.js +7 -5
  299. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  300. package/dist/editor/reviews/CommentView.js +19 -4
  301. package/dist/editor/reviews/CommentView.js.map +1 -1
  302. package/dist/editor/reviews/Comments.js +89 -72
  303. package/dist/editor/reviews/Comments.js.map +1 -1
  304. package/dist/editor/reviews/CreateReviewDialog.js +281 -177
  305. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  306. package/dist/editor/reviews/DecisionsMatrix.js +96 -25
  307. package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
  308. package/dist/editor/reviews/DiffView.js +7 -14
  309. package/dist/editor/reviews/DiffView.js.map +1 -1
  310. package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
  311. package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
  312. package/dist/editor/reviews/MultiReviewManager.js +25 -3
  313. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  314. package/dist/editor/reviews/PagesPanel.js +31 -15
  315. package/dist/editor/reviews/PagesPanel.js.map +1 -1
  316. package/dist/editor/reviews/PreviewInfo.js +1 -4
  317. package/dist/editor/reviews/PreviewInfo.js.map +1 -1
  318. package/dist/editor/reviews/ReviewCard.js +13 -7
  319. package/dist/editor/reviews/ReviewCard.js.map +1 -1
  320. package/dist/editor/reviews/ReviewDetail.js +3 -2
  321. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  322. package/dist/editor/reviews/ReviewsList.js +7 -3
  323. package/dist/editor/reviews/ReviewsList.js.map +1 -1
  324. package/dist/editor/reviews/SuggestedEdit.js +34 -3
  325. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  326. package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
  327. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  328. package/dist/editor/reviews/commentAi.js +25 -6
  329. package/dist/editor/reviews/commentAi.js.map +1 -1
  330. package/dist/editor/reviews/reviewCommands.js +4 -1
  331. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  332. package/dist/editor/reviews/useMultiReview.js +2 -2
  333. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  334. package/dist/editor/reviews/useReviews.d.ts +2 -2
  335. package/dist/editor/reviews/useReviews.js +12 -30
  336. package/dist/editor/reviews/useReviews.js.map +1 -1
  337. package/dist/editor/services/agentService.d.ts +240 -5
  338. package/dist/editor/services/agentService.js +299 -39
  339. package/dist/editor/services/agentService.js.map +1 -1
  340. package/dist/editor/services/aiService.d.ts +57 -1
  341. package/dist/editor/services/aiService.js +79 -6
  342. package/dist/editor/services/aiService.js.map +1 -1
  343. package/dist/editor/services/contentService.d.ts +6 -3
  344. package/dist/editor/services/contentService.js +13 -12
  345. package/dist/editor/services/contentService.js.map +1 -1
  346. package/dist/editor/services/editService.d.ts +52 -1
  347. package/dist/editor/services/editService.js +94 -2
  348. package/dist/editor/services/editService.js.map +1 -1
  349. package/dist/editor/services/indexService.js +1 -1
  350. package/dist/editor/services/indexService.js.map +1 -1
  351. package/dist/editor/services/reviewsService.d.ts +3 -6
  352. package/dist/editor/services/reviewsService.js +2 -11
  353. package/dist/editor/services/reviewsService.js.map +1 -1
  354. package/dist/editor/services/serviceHelper.d.ts +2 -1
  355. package/dist/editor/services/serviceHelper.js +112 -20
  356. package/dist/editor/services/serviceHelper.js.map +1 -1
  357. package/dist/editor/services/systemService.d.ts +2 -1
  358. package/dist/editor/services/systemService.js +3 -0
  359. package/dist/editor/services/systemService.js.map +1 -1
  360. package/dist/editor/services-server/api.d.ts +1 -2
  361. package/dist/editor/services-server/api.js +11 -6
  362. package/dist/editor/services-server/api.js.map +1 -1
  363. package/dist/editor/settings/About.js +317 -3
  364. package/dist/editor/settings/About.js.map +1 -1
  365. package/dist/editor/settings/IndexOverview.js +3 -1
  366. package/dist/editor/settings/IndexOverview.js.map +1 -1
  367. package/dist/editor/settings/QuotaInfo.js +210 -4
  368. package/dist/editor/settings/QuotaInfo.js.map +1 -1
  369. package/dist/editor/settings/SettingsView.js +25 -23
  370. package/dist/editor/settings/SettingsView.js.map +1 -1
  371. package/dist/editor/settings/Status.js +7 -6
  372. package/dist/editor/settings/Status.js.map +1 -1
  373. package/dist/editor/settings/index/CollectionWarningsDisplay.d.ts +10 -0
  374. package/dist/editor/settings/index/CollectionWarningsDisplay.js +16 -0
  375. package/dist/editor/settings/index/CollectionWarningsDisplay.js.map +1 -0
  376. package/dist/editor/settings/index/useIndexStatus.js +23 -22
  377. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  378. package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
  379. package/dist/editor/settings/panels/AgentsPanel.js +95 -121
  380. package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
  381. package/dist/editor/settings/panels/ModelsPanel.js +329 -108
  382. package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
  383. package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
  384. package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
  385. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  386. package/dist/editor/settings/panels/SearchConfigPanel.js +67 -6
  387. package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
  388. package/dist/editor/settings/panels/StatusPanel.js +7 -2
  389. package/dist/editor/settings/panels/StatusPanel.js.map +1 -1
  390. package/dist/editor/settings/panels/index.d.ts +3 -2
  391. package/dist/editor/settings/panels/index.js +3 -2
  392. package/dist/editor/settings/panels/index.js.map +1 -1
  393. package/dist/editor/settings/status/coreStatusChecks.js +124 -19
  394. package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
  395. package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
  396. package/dist/editor/settings/status/useStartupChecks.js +9 -5
  397. package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
  398. package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
  399. package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
  400. package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
  401. package/dist/editor/sidebar/ComponentPalette.js +2 -1
  402. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  403. package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
  404. package/dist/editor/sidebar/ComponentTree.js +216 -69
  405. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  406. package/dist/editor/sidebar/EditHistory.js +22 -46
  407. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  408. package/dist/editor/sidebar/Favorites.js +4 -8
  409. package/dist/editor/sidebar/Favorites.js.map +1 -1
  410. package/dist/editor/sidebar/MainContentTree.js +4 -3
  411. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  412. package/dist/editor/sidebar/OperationItem.js +21 -7
  413. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  414. package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
  415. package/dist/editor/sidebar/SidebarPanel.js +44 -12
  416. package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
  417. package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
  418. package/dist/editor/sidebar/SidebarStack.js +4 -3
  419. package/dist/editor/sidebar/SidebarStack.js.map +1 -1
  420. package/dist/editor/sidebar/Validation.js +24 -12
  421. package/dist/editor/sidebar/Validation.js.map +1 -1
  422. package/dist/editor/sidebar/Workbox.js +53 -3
  423. package/dist/editor/sidebar/Workbox.js.map +1 -1
  424. package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
  425. package/dist/editor/sidebar/WorkspaceRail.js +56 -167
  426. package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
  427. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +3 -2
  428. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
  429. package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
  430. package/dist/editor/tree-indicators/GutterColumns.js +26 -5
  431. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  432. package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
  433. package/dist/editor/tree-indicators/GutterContext.js +23 -0
  434. package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
  435. package/dist/editor/tree-indicators/index.d.ts +0 -1
  436. package/dist/editor/tree-indicators/index.js +0 -1
  437. package/dist/editor/tree-indicators/index.js.map +1 -1
  438. package/dist/editor/tree-indicators/types.d.ts +12 -1
  439. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
  440. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
  441. package/dist/editor/ui/Icons.js +1 -1
  442. package/dist/editor/ui/Icons.js.map +1 -1
  443. package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
  444. package/dist/editor/ui/ItemNameDialogNew.js +33 -17
  445. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  446. package/dist/editor/ui/ItemSearch.js +7 -11
  447. package/dist/editor/ui/ItemSearch.js.map +1 -1
  448. package/dist/editor/ui/SimpleIconButton.js +1 -1
  449. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  450. package/dist/editor/ui/SimpleTabs.d.ts +1 -0
  451. package/dist/editor/ui/SimpleTabs.js +45 -25
  452. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  453. package/dist/editor/ui/Splitter.d.ts +1 -0
  454. package/dist/editor/ui/Splitter.js +102 -86
  455. package/dist/editor/ui/Splitter.js.map +1 -1
  456. package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
  457. package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
  458. package/dist/editor/ui/TreeListSelector.d.ts +6 -1
  459. package/dist/editor/ui/TreeListSelector.js +2 -2
  460. package/dist/editor/ui/TreeListSelector.js.map +1 -1
  461. package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
  462. package/dist/editor/utils/keyboardNavigation.js +48 -140
  463. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  464. package/dist/editor/utils.js +19 -9
  465. package/dist/editor/utils.js.map +1 -1
  466. package/dist/editor/views/CompareView.d.ts +3 -1
  467. package/dist/editor/views/CompareView.js +7 -5
  468. package/dist/editor/views/CompareView.js.map +1 -1
  469. package/dist/editor/views/EditView.js +1 -1
  470. package/dist/editor/views/EditView.js.map +1 -1
  471. package/dist/editor/views/EditorSlot.js +27 -34
  472. package/dist/editor/views/EditorSlot.js.map +1 -1
  473. package/dist/editor/views/ItemEditor.js +7 -3
  474. package/dist/editor/views/ItemEditor.js.map +1 -1
  475. package/dist/editor/views/MediaFolderEditView.js +1 -1
  476. package/dist/editor/views/MediaFolderEditView.js.map +1 -1
  477. package/dist/editor/views/ParheliaView.js +5 -6
  478. package/dist/editor/views/ParheliaView.js.map +1 -1
  479. package/dist/editor/views/SingleEditView.d.ts +2 -1
  480. package/dist/editor/views/SingleEditView.js +10 -8
  481. package/dist/editor/views/SingleEditView.js.map +1 -1
  482. package/dist/editor/views/editorSlotContext.js +35 -6
  483. package/dist/editor/views/editorSlotContext.js.map +1 -1
  484. package/dist/index.d.ts +16 -2
  485. package/dist/index.js +11 -0
  486. package/dist/index.js.map +1 -1
  487. package/dist/revision.d.ts +2 -2
  488. package/dist/revision.js +2 -2
  489. package/dist/setup/services/setupWizardService.d.ts +49 -13
  490. package/dist/setup/services/setupWizardService.js +52 -17
  491. package/dist/setup/services/setupWizardService.js.map +1 -1
  492. package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
  493. package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
  494. package/dist/setup/wizard/steps/ImportModelDialog.js +39 -22
  495. package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
  496. package/dist/splash-screen/ModernSplashScreen.js +112 -32
  497. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  498. package/dist/splash-screen/NewPage.js +33 -50
  499. package/dist/splash-screen/NewPage.js.map +1 -1
  500. package/dist/splash-screen/OpenPage.js +2 -6
  501. package/dist/splash-screen/OpenPage.js.map +1 -1
  502. package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
  503. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  504. package/dist/splash-screen/ParheliaLogo.js +87 -37
  505. package/dist/splash-screen/ParheliaLogo.js.map +1 -1
  506. package/dist/splash-screen/RecentPages.js +3 -3
  507. package/dist/splash-screen/RecentPages.js.map +1 -1
  508. package/dist/tour/Tour.d.ts +2 -1
  509. package/dist/tour/Tour.js +256 -75
  510. package/dist/tour/Tour.js.map +1 -1
  511. package/dist/tour/default-tour.js +222 -96
  512. package/dist/tour/default-tour.js.map +1 -1
  513. package/dist/types.d.ts +69 -29
  514. package/package.json +19 -15
  515. package/styles.css +39 -10
  516. package/dist/editor/ComponentInfo.d.ts +0 -4
  517. package/dist/editor/ComponentInfo.js +0 -41
  518. package/dist/editor/ComponentInfo.js.map +0 -1
  519. package/dist/editor/ai/HelpTerminal.d.ts +0 -5
  520. package/dist/editor/ai/HelpTerminal.js +0 -166
  521. package/dist/editor/ai/HelpTerminal.js.map +0 -1
  522. package/dist/editor/field-types/ReactQuill.d.ts +0 -125
  523. package/dist/editor/field-types/ReactQuill.js +0 -385
  524. package/dist/editor/field-types/ReactQuill.js.map +0 -1
  525. package/dist/editor/services-server/graphQL.d.ts +0 -29
  526. package/dist/editor/services-server/graphQL.js +0 -53
  527. package/dist/editor/services-server/graphQL.js.map +0 -1
  528. package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
  529. package/dist/editor/settings/AllAgentsPanel.js +0 -139
  530. package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
  531. package/dist/editor/settings/LatestFeedback.d.ts +0 -1
  532. package/dist/editor/settings/LatestFeedback.js +0 -136
  533. package/dist/editor/settings/LatestFeedback.js.map +0 -1
  534. package/dist/editor/settings/Setup.d.ts +0 -1
  535. package/dist/editor/settings/Setup.js +0 -211
  536. package/dist/editor/settings/Setup.js.map +0 -1
  537. package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
  538. package/dist/editor/settings/panels/DatabasePanel.js +0 -50
  539. package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
  540. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
  541. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
  542. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
  543. package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
  544. package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
  545. package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
  546. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
  547. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
  548. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
  549. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
  550. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
  551. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
  552. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
  553. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
  554. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
  555. package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
  556. package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
  557. package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
  558. package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
  559. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
  560. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
  561. package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
  562. package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
  563. package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
  564. package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
  565. package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
  566. package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
  567. package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
  568. package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
  569. package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
  570. package/dist/editor/sidebar/Debug.d.ts +0 -1
  571. package/dist/editor/sidebar/Debug.js +0 -70
  572. package/dist/editor/sidebar/Debug.js.map +0 -1
  573. package/dist/editor/sidebar/GraphQL.d.ts +0 -2
  574. package/dist/editor/sidebar/GraphQL.js +0 -234
  575. package/dist/editor/sidebar/GraphQL.js.map +0 -1
  576. package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
  577. package/dist/editor/sidebar/LeftToolbar.js +0 -12
  578. package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
  579. package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
  580. package/dist/editor/sidebar/NavigationSidebar.js +0 -254
  581. package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
  582. package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
  583. package/dist/editor/tree-indicators/GutterSelector.js +0 -91
  584. package/dist/editor/tree-indicators/GutterSelector.js.map +0 -1
@@ -1,15 +1,17 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useState, useEffect, useRef, useCallback, useSyncExternalStore, useMemo, startTransition, } from "react";
4
4
  import { toast } from "sonner";
5
- import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, } from "./editContext";
5
+ import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, useEditContext, } from "./editContext";
6
6
  import { fieldModificationStore } from "./fieldModificationStore";
7
- import { useRouter, useSearchParams, usePathname } from "next/navigation";
7
+ import { useRouter, useSearchParams, usePathname } from "./navigation";
8
8
  import { findComponent, getComponentById } from "../componentTreeHelper";
9
9
  import { getOperationsContext } from "./operations";
10
10
  import { handleErrorResult } from "./helpers";
11
- import { executeFieldAction as executeFieldServerAction, connectSocket, getEditHistory, getRunningOperations, releaseFieldLocks, validateItems, } from "../services/editService";
11
+ import { executeFieldAction as executeFieldServerAction, connectSocket, getEditHistory, getRunningOperations, reconnectSession, releaseFieldLocks, validateItems, } from "../services/editService";
12
12
  import { useEditorWebSocket } from "./hooks/useEditorWebSocket";
13
+ import { createEditorSocketDiagnostics, } from "./socketDiagnostics";
14
+ import { localStorageService } from "../services/localStorageService";
13
15
  import { useSocketMessageHandler } from "./hooks/useSocketMessageHandler";
14
16
  import "react-json-view-lite/dist/index.css";
15
17
  import { MediaSelector, } from "../media-selector/MediaSelector";
@@ -20,6 +22,7 @@ import { getItemDescriptor } from "../utils";
20
22
  import { EditContextMenu } from "../ContextMenu";
21
23
  import { InlineAiTrigger } from "../ai/InlineAiTrigger";
22
24
  import { FieldEditorPopup } from "../FieldEditorPopup";
25
+ import { ConcurrentUserLimitDialog } from "../ConcurrentUserLimitDialog";
23
26
  import { post } from "../services/serviceHelper";
24
27
  import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
25
28
  import { useItemsRepository } from "./itemsRepository";
@@ -33,6 +36,7 @@ import { GuidanceOverlay } from "../ai/GuidanceOverlay";
33
36
  import { AgentDialogHandler } from "../ai/dialogs";
34
37
  import { usePageViewContext, } from "../page-viewer/pageViewContext";
35
38
  import { QuickItemSwitcher } from "../QuickItemSwitcher";
39
+ import { useEditorSlotContext, } from "../views/editorSlotContext";
36
40
  import { getComments, getAvailableCommentTags, } from "../services/reviewsService";
37
41
  import { useReviews } from "../reviews/useReviews";
38
42
  import uuid from "react-uuid";
@@ -49,7 +53,33 @@ import { useWorkbox } from "./hooks/useWorkbox";
49
53
  import { useMediaSelector } from "./hooks/useMediaSelector";
50
54
  import { useGlobalEditorKeyDown } from "./hooks/useGlobalEditorKeyDown";
51
55
  import { useStartupChecks } from "../settings/status/index";
52
- export function EditorShell({ configuration, className, item: loadItemDescriptor, sessionId, userInfo, userPreferences, parheliaSettings, setUserPreferences, children, }) {
56
+ import { FeatureGate, LicenseFeatures, LicenseProvider, LicenseOverlay, } from "../../licensing";
57
+ function AgentsSlotContextBridge({ slot }) {
58
+ const editContext = useEditContext();
59
+ const slotContext = useEditorSlotContext({
60
+ slotId: slot.slotId,
61
+ itemDescriptor: slot.itemDescriptor,
62
+ refreshToken: slot.refreshToken,
63
+ });
64
+ if (!editContext || !slotContext)
65
+ return null;
66
+ useEffect(() => {
67
+ editContext.registerSlotContext(slot.slotId, slotContext);
68
+ return () => {
69
+ editContext.unregisterSlotContext(slot.slotId);
70
+ };
71
+ }, [
72
+ slot.slotId,
73
+ slotContext,
74
+ editContext.registerSlotContext,
75
+ editContext.unregisterSlotContext,
76
+ ]);
77
+ return null;
78
+ }
79
+ function AgentsSlotContextBridgeHost({ slots }) {
80
+ return (_jsx(_Fragment, { children: slots.map((slot) => (_jsx(AgentsSlotContextBridge, { slot: slot }, `agents-slot-bridge-${slot.slotId}`))) }));
81
+ }
82
+ export function EditorShell({ configuration, className, item: loadItemDescriptor, sessionId, userInfo, userPreferences, initialLicenseStatus, initialLicenseStatusLoaded, parheliaSettings, setUserPreferences, children, }) {
53
83
  const router = useRouter();
54
84
  const pathname = usePathname();
55
85
  const searchParams = useSearchParams();
@@ -97,6 +127,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
97
127
  const [historyMode, setHistoryMode] = useState("global");
98
128
  const [showOnlyMyChanges, setShowOnlyMyChanges] = useState(true);
99
129
  const [filterByCurrentLanguage, setFilterByCurrentLanguage] = useState(true);
130
+ const [historySearchQuery, setHistorySearchQuery] = useState("");
100
131
  const [recentEdits, setRecentEdits] = useState([]);
101
132
  const addRecentEdit = useCallback((edit) => {
102
133
  setRecentEdits((prevEditedFields) => {
@@ -124,14 +155,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
124
155
  if (typeof window !== "undefined")
125
156
  sessionStorage?.setItem("sessionId", sessionId);
126
157
  // Workspace state
127
- // Note: "reviews" is a sidebar, not a workspace. If view/workspace=reviews, we should
158
+ // Note: "reviews" is a sidebar, not a workspace. If workspace=reviews, we should
128
159
  // set workspace to "editor" and open the reviews sidebar instead.
129
160
  // Memoize searchParams reads to avoid triggering Router state updates during render
130
161
  // (Next.js App Router uses startTransition internally for URL changes)
131
- const rawWorkspace = useMemo(() => searchParams.get("workspace") ?? searchParams.get("view"), [searchParams]);
162
+ const rawWorkspace = useMemo(() => searchParams.get("workspace"), [searchParams]);
132
163
  const isReviewsSidebarRequest = rawWorkspace === "reviews";
133
164
  const [workspaceId, setWorkspaceId] = useState(
134
- // If view=reviews, use "editor" workspace (reviews is a sidebar, not a workspace)
165
+ // If workspace=reviews, use "editor" workspace (reviews is a sidebar, not a workspace)
135
166
  isReviewsSidebarRequest
136
167
  ? "editor"
137
168
  : (rawWorkspace ??
@@ -153,7 +184,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
153
184
  else {
154
185
  sidebars = [...(configuration.editor.defaultOpenSidebars ?? [])];
155
186
  }
156
- // If view/workspace=reviews was requested, ensure reviews sidebar is open
187
+ // If workspace=reviews was requested, ensure reviews sidebar is open
157
188
  if (isReviewsSidebarRequest && !sidebars.includes("reviews")) {
158
189
  sidebars.push("reviews");
159
190
  }
@@ -161,17 +192,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
161
192
  });
162
193
  // Toolbar pinned sidebars - icons always visible in toolbar (defined early so callbacks can access)
163
194
  const [pinnedSidebars, setPinnedSidebars] = useState(userInfo.preferences?.pinnedSidebars || []);
195
+ const pinnedSidebarsRef = useRef(pinnedSidebars);
164
196
  // Locked sidebars - open panels that stay visible when selecting another sidebar
165
197
  const [lockedSidebars, setLockedSidebars] = useState(() => {
166
- if (typeof window === "undefined") {
167
- return [];
168
- }
169
198
  try {
170
- const stored = window.localStorage.getItem("editor.lockedSidebars");
171
- if (!stored) {
172
- return [];
173
- }
174
- const parsed = JSON.parse(stored);
199
+ const parsed = localStorageService.getOrSetItem("editor.lockedSidebars", []);
175
200
  if (!Array.isArray(parsed) ||
176
201
  !parsed.every((id) => typeof id === "string")) {
177
202
  return [];
@@ -187,6 +212,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
187
212
  useEffect(() => {
188
213
  lockedSidebarsRef.current = lockedSidebars;
189
214
  }, [lockedSidebars]);
215
+ useEffect(() => {
216
+ pinnedSidebarsRef.current = pinnedSidebars;
217
+ }, [pinnedSidebars]);
190
218
  // Filter locked sidebars to only include currently open sidebars
191
219
  useEffect(() => {
192
220
  const openSet = new Set(openSidebars);
@@ -248,15 +276,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
248
276
  // Group open sidebars into vertical stacks (each stack shares one left column).
249
277
  const [sidebarStacks, setSidebarStacks] = useState(() => {
250
278
  const defaultStacks = openSidebars.map((id) => [id]);
251
- if (typeof window === "undefined") {
252
- return normalizeSidebarStacks(openSidebars, defaultStacks);
253
- }
254
279
  try {
255
- const stored = window.localStorage.getItem("editor.sidebarStacks");
256
- if (!stored) {
280
+ const parsed = localStorageService.getItem("editor.sidebarStacks");
281
+ if (!parsed) {
257
282
  return normalizeSidebarStacks(openSidebars, defaultStacks);
258
283
  }
259
- const parsed = JSON.parse(stored);
260
284
  if (!Array.isArray(parsed) ||
261
285
  !parsed.every((x) => Array.isArray(x) && x.every((id) => typeof id === "string"))) {
262
286
  return normalizeSidebarStacks(openSidebars, defaultStacks);
@@ -278,28 +302,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
278
302
  }, [openSidebars, normalizeSidebarStacks]);
279
303
  // Persist stacks independently of the URL (user layout preference).
280
304
  useEffect(() => {
281
- if (typeof window === "undefined")
282
- return;
283
305
  try {
284
- window.localStorage.setItem("editor.sidebarStacks", JSON.stringify(sidebarStacks));
306
+ localStorageService.setItem("editor.sidebarStacks", sidebarStacks);
285
307
  }
286
308
  catch { }
287
309
  }, [sidebarStacks]);
288
310
  // Persist locked sidebars to localStorage.
289
311
  useEffect(() => {
290
- if (typeof window === "undefined")
291
- return;
292
312
  try {
293
- window.localStorage.setItem("editor.lockedSidebars", JSON.stringify(lockedSidebars));
313
+ localStorageService.setItem("editor.lockedSidebars", lockedSidebars);
294
314
  }
295
315
  catch { }
296
316
  }, [lockedSidebars]);
297
- // Legacy aliases for backwards compatibility
298
317
  const viewName = workspaceId;
299
- const setViewName = setWorkspaceId;
300
- const viewNameRef = workspaceIdRef;
301
- const previousViewName = previousWorkspaceId;
302
- const setPreviousViewName = setPreviousWorkspaceId;
303
318
  const [compareMode, setCompareModeState] = useState(false);
304
319
  const [componentDesignerComponent, setComponentDesignerComponent] = useState();
305
320
  const [componentDesignerRendering, setComponentDesignerRendering] = useState();
@@ -307,6 +322,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
307
322
  const [ignoreBlur, setIgnoreBlur] = useState(false);
308
323
  const [currentItemDescriptor, setCurrentItemDescriptor] = useState();
309
324
  const currentItemDescriptorRef = useRef(undefined);
325
+ const latestGlobalLoadKeyRef = useRef(null);
310
326
  const [editorSlots, setEditorSlots] = useState(() => {
311
327
  // Always start with empty slots - don't persist the number of slots
312
328
  return [];
@@ -326,6 +342,29 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
326
342
  activeSlotIdRef.current = activeSlotId;
327
343
  }, [activeSlotId]);
328
344
  const [slotContexts, setSlotContexts] = useState(() => new Map());
345
+ const promptSessionReconnect = useCallback((reason) => {
346
+ const description = reason && reason !== "session-revoked"
347
+ ? `${reason}. Click reconnect to continue in this browser.`
348
+ : "You were disconnected because this account is active in another browser.";
349
+ toast.error("Session disconnected", {
350
+ id: "session-revoked",
351
+ description,
352
+ action: {
353
+ label: "Reconnect",
354
+ onClick: async () => {
355
+ const result = await reconnectSession(sessionId);
356
+ if (result.type === "success") {
357
+ window.location.reload();
358
+ return;
359
+ }
360
+ toast.error("Reconnect failed", {
361
+ description: "Could not claim this browser session. Try again.",
362
+ });
363
+ },
364
+ },
365
+ duration: Infinity,
366
+ });
367
+ }, [sessionId]);
329
368
  // Track previous item ID to detect actual navigation between pages
330
369
  const previousItemIdRef = useRef(undefined);
331
370
  useEffect(() => {
@@ -354,10 +393,18 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
354
393
  // Ref to track when we're handling a popstate event (browser back/forward)
355
394
  // This prevents the URL sync effect from pushing new history entries during back navigation
356
395
  const isHandlingPopStateRef = useRef(false);
357
- // Ref to track the last known URL for the popstate handler
358
- // This is updated both when the popstate handler runs AND when the URL sync effect pushes a new URL
359
- // Without this, the popstate handler would have a stale lastUrl value after pushState calls
360
- const lastUrlRef = useRef(typeof window !== "undefined" ? window.location.href : "");
396
+ // When switchWorkspace already pushed a URL entry, the follow-up URL sync
397
+ // effect should only *replace* that entry (not push a second one).
398
+ const switchWorkspacePushedRef = useRef(false);
399
+ // Ref to track the last known URL for the popstate handler.
400
+ // Uses pathname+search (not full href) so comparisons are consistent with the
401
+ // relative URLs produced by the URL sync effect and updateUrl.
402
+ const lastUrlRef = useRef(typeof window !== "undefined"
403
+ ? `${window.location.pathname}${window.location.search}`
404
+ : "");
405
+ // The very first URL sync after initial load should replaceState (not pushState)
406
+ // so the initial bare URL isn't left as a dead-end history entry.
407
+ const isFirstUrlSyncRef = useRef(true);
361
408
  const [inlineEditingFieldElement, setInlineEditingFieldElement] = useState();
362
409
  const [lockedField, setLockedField] = useState();
363
410
  const [itemLanguages, setItemLanguages] = useState([]);
@@ -370,26 +417,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
370
417
  const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
371
418
  const [availableCommentTags, setAvailableCommentTags] = useState([]);
372
419
  const [showComments, setShowComments] = useState(() => {
373
- const savedShowComments = typeof window !== "undefined"
374
- ? localStorage.getItem("editor.showComments")
375
- : null;
376
- return savedShowComments ? JSON.parse(savedShowComments) : true;
420
+ return localStorageService.getOrSetItem("editor.showComments", true);
377
421
  });
378
422
  useEffect(() => {
379
- if (typeof window !== "undefined") {
380
- localStorage.setItem("editor.showComments", JSON.stringify(showComments));
381
- }
423
+ localStorageService.setItem("editor.showComments", showComments);
382
424
  }, [showComments]);
383
425
  const [showResolvedComments, setShowResolvedComments] = useState(() => {
384
- const saved = typeof window !== "undefined"
385
- ? localStorage.getItem("editor.showResolvedComments")
386
- : null;
387
- return saved ? JSON.parse(saved) : false;
426
+ return localStorageService.getOrSetItem("editor.showResolvedComments", false);
388
427
  });
389
428
  useEffect(() => {
390
- if (typeof window !== "undefined") {
391
- localStorage.setItem("editor.showResolvedComments", JSON.stringify(showResolvedComments));
392
- }
429
+ localStorageService.setItem("editor.showResolvedComments", showResolvedComments);
393
430
  }, [showResolvedComments]);
394
431
  const [selectedComment, setSelectedComment] = useState();
395
432
  const [browseHistory, setBrowseHistory] = useState(() => {
@@ -408,13 +445,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
408
445
  visitedAt: entry.visitedAt,
409
446
  }));
410
447
  });
411
- // Navigation history for browser history (view + item combinations)
448
+ // Navigation history for browser history (workspace + item combinations)
412
449
  const [navigationHistory, setNavigationHistory] = useState(() => {
413
- // Initialize from browse history with current view
450
+ // Initialize from browse history with the current workspace
414
451
  if (!userInfo.browseHistory)
415
452
  return [];
416
453
  const defaultWorkspaceId = searchParams.get("workspace") ??
417
- searchParams.get("view") ??
418
454
  configuration.editor.defaultWorkspace ??
419
455
  configuration.editor.workspaces?.[0]?.id ??
420
456
  "editor";
@@ -446,20 +482,43 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
446
482
  const [statusMessage, setStatusMessage] = useState("");
447
483
  const [focusFieldComponentId, setFocusFieldComponentId] = useState();
448
484
  const [enableCompletions, setEnableCompletions] = useState(false);
449
- const [showComponentNavigator, setShowComponentNavigator] = useState(userPreferences.showComponentNavigator ?? false);
485
+ const [showComponentNavigatorDefault, setShowComponentNavigatorDefault] = useState(userPreferences.showComponentNavigator ?? false);
486
+ const [slotComponentNavigatorVisibility, setSlotComponentNavigatorVisibility] = useState({});
487
+ useEffect(() => {
488
+ setSlotComponentNavigatorVisibility((prev) => {
489
+ const next = {};
490
+ let changed = false;
491
+ for (const slot of editorSlots) {
492
+ if (Object.prototype.hasOwnProperty.call(prev, slot.slotId)) {
493
+ next[slot.slotId] = prev[slot.slotId];
494
+ }
495
+ else {
496
+ next[slot.slotId] = showComponentNavigatorDefault;
497
+ changed = true;
498
+ }
499
+ }
500
+ if (Object.keys(prev).length !== editorSlots.length) {
501
+ changed = true;
502
+ }
503
+ return changed ? next : prev;
504
+ });
505
+ }, [editorSlots, showComponentNavigatorDefault]);
450
506
  const [showAgentsPanel, setShowAgentsPanel] = useState(userPreferences.showAgentsPanel ?? false);
451
507
  const [showMinimap, setShowMinimap] = useState(userPreferences.showMinimap ?? true);
452
508
  const [showHelpTerminal, setShowHelpTerminal] = useState(false);
453
509
  const [helpTerminalInitialPrompt, setHelpTerminalInitialPrompt] = useState(undefined);
454
510
  const [helpTerminalProfileName, setHelpTerminalProfileName] = useState(undefined);
455
511
  const [helpTerminalActiveTab, setHelpTerminalActiveTab] = useState(undefined);
456
- const [showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor] = useState(true);
512
+ const [selectedHelpSectionId, setSelectedHelpSectionId] = useState(null);
513
+ const [showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor] = useState(false);
514
+ const [selectedAgentsWorkspaceAgentId, setSelectedAgentsWorkspaceAgentId] = useState(null);
457
515
  const [activeEditorTab, setActiveEditorTab] = useState(null);
458
516
  const [showLayoutComponents, setShowLayoutComponents] = useState(userPreferences.showLayoutComponents ?? false);
459
517
  const { quotaInfo, setQuotaInfo, isQuotaExceeded, getQuotaWarningMessage } = useQuota({
460
518
  showError: ({ summary, details }) => showErrorToast({ summary, details }),
461
519
  });
462
520
  const [webSocketMessages, setWebSocketMessages] = useState([]);
521
+ const [socketDiagnostics, setSocketDiagnostics] = useState(() => createEditorSocketDiagnostics(sessionId));
463
522
  const [favorites, setFavorites] = useState([]);
464
523
  // Quick item switcher state
465
524
  const [quickSwitcherVisible, setQuickSwitcherVisible] = useState(false);
@@ -477,13 +536,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
477
536
  if (!startupChecks.hasBlockingIssues)
478
537
  return;
479
538
  // Don't redirect if already in settings workspace - let user navigate freely within settings
480
- const currentWorkspace = searchParams.get("workspace") ?? searchParams.get("view");
539
+ const currentWorkspace = searchParams.get("workspace");
481
540
  if (currentWorkspace === "settings")
482
541
  return;
483
542
  // Redirect to the status panel (where user can see all issues and navigate to fixes)
484
543
  const url = new URL(window.location.href);
485
544
  url.searchParams.set("workspace", "settings");
486
- url.searchParams.delete("view"); // Remove legacy param
487
545
  url.searchParams.set("ccpanel", "status");
488
546
  router.push(url.toString(), { scroll: false });
489
547
  }, [
@@ -501,6 +559,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
501
559
  setMode(queryMode);
502
560
  }
503
561
  }, [searchParams, isInitialLoad]);
562
+ useEffect(() => {
563
+ if (!isInitialLoad)
564
+ return;
565
+ const helpParam = searchParams.get("help");
566
+ if (helpParam) {
567
+ setHelpTerminalActiveTab("manual");
568
+ setShowHelpTerminal(true);
569
+ setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true" ? null : helpParam);
570
+ }
571
+ }, [searchParams, isInitialLoad]);
504
572
  useEffect(() => {
505
573
  if (mode === "suggestions") {
506
574
  // Ensure we're in the editor workspace and open the feedback sidebar
@@ -547,12 +615,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
547
615
  setSlotContexts((prev) => {
548
616
  if (!prev.has(slotId))
549
617
  return prev;
618
+ if (slotId === activeSlotIdRef.current) {
619
+ const activeCtx = prev.get(slotId);
620
+ if (activeCtx) {
621
+ lastActiveSlotContextRef.current = activeCtx;
622
+ }
623
+ }
550
624
  const next = new Map(prev);
551
625
  next.delete(slotId);
552
626
  return next;
553
627
  });
554
628
  }, []);
555
629
  const slotContextsRef = useRef(slotContexts);
630
+ const lastActiveSlotContextRef = useRef(null);
556
631
  useEffect(() => {
557
632
  slotContextsRef.current = slotContexts;
558
633
  }, [slotContexts]);
@@ -578,6 +653,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
578
653
  const activeSlotContext = activeSlotId
579
654
  ? slotContexts.get(activeSlotId)
580
655
  : undefined;
656
+ const isComponentNavigatorOpenForSlot = useCallback((slotId) => {
657
+ if (!slotId)
658
+ return showComponentNavigatorDefault;
659
+ return (slotComponentNavigatorVisibility[slotId] ?? showComponentNavigatorDefault);
660
+ }, [slotComponentNavigatorVisibility, showComponentNavigatorDefault]);
661
+ const showComponentNavigator = isComponentNavigatorOpenForSlot(activeSlotId);
581
662
  // Sync global compareMode from the active slot's compareMode for URL syncing
582
663
  useEffect(() => {
583
664
  if (activeSlotContext && activeSlotContext.compareMode !== compareMode) {
@@ -625,6 +706,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
625
706
  const isItemUsedInCurrentPage = useCallback((item) => pageItemsSetRef.current.has(makeItemKey(item)), [makeItemKey]);
626
707
  const socketMessageListeners = useRef(new Set());
627
708
  const [socketConnectionVersion, setSocketConnectionVersion] = useState(0);
709
+ useEffect(() => {
710
+ setSocketDiagnostics(createEditorSocketDiagnostics(sessionId));
711
+ }, [sessionId]);
628
712
  const addSocketMessageListener = useCallback((callback) => {
629
713
  socketMessageListeners.current.add(callback);
630
714
  return () => socketMessageListeners.current.delete(callback);
@@ -709,8 +793,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
709
793
  console.error(`No workspace found for id: ${workspaceId}`);
710
794
  return null;
711
795
  }
712
- // Legacy alias for backwards compatibility
713
- const currentView = currentWorkspace;
796
+ const activeKeyboardCommands = useMemo(() => {
797
+ const activeCommandIds = new Set(currentWorkspace.keyboardCommandIds ?? []);
798
+ return (configuration.commands.keyboardCommands ?? []).filter((command) => activeCommandIds.has(command.id));
799
+ }, [
800
+ currentWorkspace.keyboardCommandIds,
801
+ configuration.commands.keyboardCommands,
802
+ ]);
714
803
  useEffect(() => {
715
804
  if (currentWorkspace?.component) {
716
805
  setCenterPanelView(currentWorkspace.component);
@@ -738,6 +827,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
738
827
  const sendClientInfo = useCallback(() => {
739
828
  debouncedSendClientInfo();
740
829
  }, [debouncedSendClientInfo]);
830
+ const getCurrentHistoryState = useCallback(() => {
831
+ if (typeof window === "undefined") {
832
+ return null;
833
+ }
834
+ return window.history.state;
835
+ }, []);
836
+ useEffect(() => {
837
+ if (isInitialLoad) {
838
+ return;
839
+ }
840
+ const itemid = searchParams.get("itemid");
841
+ const language = searchParams.get("lang") ?? searchParams.get("language");
842
+ const versionParam = searchParams.get("version");
843
+ const version = versionParam ? parseInt(versionParam, 10) : 0;
844
+ const itemId = cleanId(itemid ?? undefined);
845
+ if (!itemId || !language) {
846
+ return;
847
+ }
848
+ const currentDescriptor = currentItemDescriptorRef.current;
849
+ const matchesCurrentDescriptor = currentDescriptor?.id === itemId &&
850
+ currentDescriptor?.language === language &&
851
+ (!version || currentDescriptor?.version === version);
852
+ if (matchesCurrentDescriptor) {
853
+ return;
854
+ }
855
+ isHandlingPopStateRef.current = true;
856
+ loadItemRef
857
+ .current({
858
+ id: itemId,
859
+ language,
860
+ version,
861
+ }, {
862
+ addToBrowseHistory: false,
863
+ skipViewChange: true,
864
+ })
865
+ .finally(() => {
866
+ isHandlingPopStateRef.current = false;
867
+ });
868
+ }, [isInitialLoad, searchParams]);
741
869
  const startTour = useCallback(() => {
742
870
  setIsTourActive(true);
743
871
  // Persist tour state to URL so it survives navigation/remounts
@@ -745,32 +873,61 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
745
873
  const params = new URLSearchParams(window.location.search);
746
874
  params.set("tour", "active");
747
875
  const newUrl = `${window.location.pathname}?${params.toString()}`;
748
- window.history.replaceState(null, "", newUrl);
876
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
749
877
  }, [setIsTourActive]);
750
- const isMobile = useMediaQuery("(max-width: 768px)");
751
- const handleSetShowComponentNavigator = useCallback((value) => {
752
- const newValue = typeof value === "function" ? value(showComponentNavigator) : value;
753
- setShowComponentNavigator(newValue);
878
+ const isMobile = useMediaQuery("(max-width: 767px)");
879
+ // On mobile, clear all locked sidebars and keep only the last-opened panel.
880
+ // Handles viewport resize: desktop -> mobile unlocks everything and trims to one panel.
881
+ useEffect(() => {
882
+ if (isMobile) {
883
+ setLockedSidebars([]);
884
+ const current = openSidebarsRef.current;
885
+ const lastSidebar = current[current.length - 1];
886
+ if (current.length > 1 && lastSidebar) {
887
+ const trimmed = [lastSidebar];
888
+ openSidebarsRef.current = trimmed;
889
+ setOpenSidebars(trimmed);
890
+ }
891
+ }
892
+ }, [isMobile]);
893
+ const setComponentNavigatorOpenForSlot = useCallback((slotId, value) => {
894
+ const previousValue = isComponentNavigatorOpenForSlot(slotId);
895
+ const newValue = typeof value === "function" ? value(previousValue) : value;
896
+ if (slotId) {
897
+ setSlotComponentNavigatorVisibility((prev) => {
898
+ const currentValue = prev[slotId] ?? showComponentNavigatorDefault;
899
+ if (currentValue === newValue && prev[slotId] !== undefined) {
900
+ return prev;
901
+ }
902
+ return {
903
+ ...prev,
904
+ [slotId]: newValue,
905
+ };
906
+ });
907
+ }
908
+ setShowComponentNavigatorDefault(newValue);
754
909
  setUserPreferences({ showComponentNavigator: newValue });
755
- // On mobile, close Agents Panel when opening Component Navigator
756
910
  if (isMobile && newValue) {
757
911
  setShowAgentsPanel(false);
758
912
  setUserPreferences({ showAgentsPanel: false });
759
913
  }
760
914
  }, [
761
- showComponentNavigator,
762
- setShowComponentNavigator,
915
+ isComponentNavigatorOpenForSlot,
916
+ showComponentNavigatorDefault,
763
917
  setUserPreferences,
764
918
  isMobile,
765
919
  setShowAgentsPanel,
766
920
  ]);
921
+ const handleSetShowComponentNavigator = useCallback((value) => {
922
+ setComponentNavigatorOpenForSlot(activeSlotIdRef.current, value);
923
+ }, [setComponentNavigatorOpenForSlot]);
767
924
  const handleSetShowAgentsPanel = useCallback((value) => {
768
925
  const newValue = typeof value === "function" ? value(showAgentsPanel) : value;
769
926
  setShowAgentsPanel(newValue);
770
927
  setUserPreferences({ showAgentsPanel: newValue });
771
928
  // On mobile, close Component Navigator when opening Agents Panel
772
929
  if (isMobile && newValue) {
773
- setShowComponentNavigator(false);
930
+ setComponentNavigatorOpenForSlot(activeSlotIdRef.current, false);
774
931
  setUserPreferences({ showComponentNavigator: false });
775
932
  }
776
933
  }, [
@@ -778,8 +935,40 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
778
935
  setShowAgentsPanel,
779
936
  setUserPreferences,
780
937
  isMobile,
781
- setShowComponentNavigator,
938
+ setComponentNavigatorOpenForSlot,
782
939
  ]);
940
+ // Mobile editor panel state (EditorForm shown in bottom panel on mobile)
941
+ const [mobileEditorPanelOpen, setMobileEditorPanelOpenRaw] = useState(false);
942
+ const [dismissedMobilePanelToken, setDismissedMobilePanelToken] = useState(null);
943
+ const previousActiveSlotIdRef = useRef(null);
944
+ const mobilePanelDismissToken = useMemo(() => {
945
+ const selectionKey = selection.join(",");
946
+ return [
947
+ activeSlotId || "no-slot",
948
+ selectionKey,
949
+ insertMode ? "insert" : "browse",
950
+ ].join("|");
951
+ }, [activeSlotId, insertMode, selection]);
952
+ useEffect(() => {
953
+ const previousActiveSlotId = previousActiveSlotIdRef.current;
954
+ if (previousActiveSlotId !== activeSlotId) {
955
+ setDismissedMobilePanelToken(null);
956
+ }
957
+ previousActiveSlotIdRef.current = activeSlotId;
958
+ }, [activeSlotId]);
959
+ const handleSetMobileEditorPanelOpen = useCallback((open) => {
960
+ setMobileEditorPanelOpenRaw(open);
961
+ if (!open) {
962
+ setDismissedMobilePanelToken(mobilePanelDismissToken);
963
+ return;
964
+ }
965
+ setDismissedMobilePanelToken(null);
966
+ if (open && isMobile) {
967
+ // Close all sidebars when opening the editor panel on mobile
968
+ openSidebarsRef.current = [];
969
+ setOpenSidebars([]);
970
+ }
971
+ }, [isMobile, mobilePanelDismissToken]);
783
972
  const handleSetShowMinimap = useCallback((value) => {
784
973
  const newValue = typeof value === "function" ? value(showMinimap) : value;
785
974
  setShowMinimap(newValue);
@@ -793,6 +982,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
793
982
  setHelpTerminalInitialPrompt(undefined);
794
983
  setHelpTerminalProfileName(undefined);
795
984
  setHelpTerminalActiveTab(undefined);
985
+ setSelectedHelpSectionId(null);
796
986
  }
797
987
  }, [showHelpTerminal]);
798
988
  const toggleHelpTerminal = useCallback((options) => {
@@ -835,13 +1025,27 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
835
1025
  setOpenSidebars(newOrder);
836
1026
  setSidebarStacks((prev) => normalizeSidebarStacks(newOrder, prev));
837
1027
  }, []);
1028
+ const ensureSidebarPinned = useCallback((sidebarId) => {
1029
+ const currentPinnedSidebars = pinnedSidebarsRef.current;
1030
+ if (currentPinnedSidebars.includes(sidebarId)) {
1031
+ return;
1032
+ }
1033
+ const newPinnedSidebars = [...currentPinnedSidebars, sidebarId];
1034
+ pinnedSidebarsRef.current = newPinnedSidebars;
1035
+ setPinnedSidebars(newPinnedSidebars);
1036
+ setUserPreferences({ pinnedSidebars: newPinnedSidebars });
1037
+ }, [setUserPreferences]);
838
1038
  // messageHandler is defined after loadItem/loadHistory declarations to avoid temporal dead zones
839
1039
  const user = activeSessions.find((x) => x.sessionId === sessionId)?.user ||
840
1040
  userInfo.user;
841
- // Self-heal if our session disappears (e.g., after HMR)
1041
+ // Self-heal if our session disappears (e.g., after HMR). Skip when concurrent user limit
1042
+ // is showing so we don't spam recovery attempts when the connection was rejected.
842
1043
  const missingSessionRecoveryTimerRef = useRef(null);
843
1044
  const missingSessionRecoveryAttemptsRef = useRef(0);
1045
+ const concurrentUserLimitErrorRef = useRef(null);
844
1046
  useEffect(() => {
1047
+ if (concurrentUserLimitErrorRef.current !== null)
1048
+ return;
845
1049
  const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
846
1050
  if (hasMySession) {
847
1051
  // Reset recovery state when we see ourselves again
@@ -857,7 +1061,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
857
1061
  return;
858
1062
  const attempt = missingSessionRecoveryAttemptsRef.current + 1;
859
1063
  const delay = Math.min(250 * Math.pow(2, attempt - 1), 2000);
860
- console.warn(`⚠️ Current session not present in active sessions. Recovery attempt ${attempt} in ${delay}ms...`);
861
1064
  missingSessionRecoveryTimerRef.current = setTimeout(() => {
862
1065
  missingSessionRecoveryTimerRef.current = null;
863
1066
  missingSessionRecoveryAttemptsRef.current = attempt;
@@ -866,6 +1069,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
866
1069
  }
867
1070
  else {
868
1071
  // Force a reconnect to refresh presence after several failed nudges
1072
+ console.warn("Session presence did not recover after retries. Forcing reconnect.");
869
1073
  try {
870
1074
  globalThis.editorSocket?.close(4000, "recover-presence");
871
1075
  }
@@ -885,9 +1089,15 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
885
1089
  // Initialize lastUrlRef to current URL on mount
886
1090
  lastUrlRef.current = window.location.href;
887
1091
  const keepAliveUrl = "/parhelia/keepalive";
888
- const interval = setInterval(() => {
1092
+ const runSessionCheck = () => {
889
1093
  fetch(keepAliveUrl + "?ts=" + Date.now())
890
1094
  .then((response) => {
1095
+ if (response.headers.get("X-Parhelia-Session-Revoked") === "true") {
1096
+ window.dispatchEvent(new CustomEvent("parhelia:session-revoked", {
1097
+ detail: { reason: "session-revoked" },
1098
+ }));
1099
+ return;
1100
+ }
891
1101
  if (response.status === 401 || response.status === 403) {
892
1102
  toast.error("Your session has expired", {
893
1103
  description: "Please login again to continue editing.",
@@ -900,7 +1110,24 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
900
1110
  }
901
1111
  })
902
1112
  .catch((error) => console.error("Keep Alive error:", error));
903
- }, 5 * 60 * 1000);
1113
+ };
1114
+ const keepaliveIntervalMs = (() => {
1115
+ const param = new URLSearchParams(window.location.search).get("keepaliveIntervalMs");
1116
+ if (!param)
1117
+ return 5 * 60 * 1000;
1118
+ const ms = parseInt(param, 10);
1119
+ return Number.isFinite(ms) && ms >= 2000 && ms <= 60000
1120
+ ? ms
1121
+ : 5 * 60 * 1000;
1122
+ })();
1123
+ const interval = setInterval(() => {
1124
+ runSessionCheck();
1125
+ }, keepaliveIntervalMs);
1126
+ const handleVisibilityChange = () => {
1127
+ if (document.visibilityState === "visible") {
1128
+ runSessionCheck();
1129
+ }
1130
+ };
904
1131
  const handleMessage = (event) => {
905
1132
  if (event.data.type === "componentsSelected") {
906
1133
  setSelection(event.data.componentIds);
@@ -908,35 +1135,44 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
908
1135
  };
909
1136
  // Listen for browser navigation events (back/forward buttons)
910
1137
  const handlePopState = (event) => {
911
- const newUrl = window.location.href;
1138
+ const newUrl = `${window.location.pathname}${window.location.search}`;
912
1139
  if (newUrl !== lastUrlRef.current) {
913
1140
  lastUrlRef.current = newUrl;
914
1141
  // Mark that we're handling a popstate to prevent URL sync from pushing to history
915
1142
  isHandlingPopStateRef.current = true;
916
1143
  // Sync URL parameters back to component state for browser navigation
917
1144
  const urlParams = new URLSearchParams(window.location.search);
918
- // Handle workspace changes (with legacy view fallback)
919
- const urlWorkspace = urlParams.get("workspace") ?? urlParams.get("view");
920
- if (urlWorkspace && urlWorkspace !== viewNameRef.current) {
1145
+ const urlWorkspace = urlParams.get("workspace");
1146
+ if (urlWorkspace && urlWorkspace !== workspaceIdRef.current) {
921
1147
  setWorkspaceId(urlWorkspace);
922
1148
  }
923
- // Handle sidebar changes
1149
+ // Handle sidebar changes — clear when absent so state matches the URL
924
1150
  const sidebarParam = urlParams.get("sidebar");
925
- if (sidebarParam !== null) {
926
- const newSidebars = sidebarParam.split(",").filter(Boolean);
927
- setOpenSidebars(newSidebars);
928
- }
1151
+ const newSidebars = sidebarParam
1152
+ ? sidebarParam.split(",").filter(Boolean)
1153
+ : [];
1154
+ setOpenSidebars(newSidebars);
929
1155
  // Handle wizard ID changes
930
1156
  const wizardId = urlParams.get("wizardid");
931
1157
  setCurrentWizardId(wizardId);
932
- // Handle compare mode changes
933
- const compareValue = urlParams.get("compare") === "true";
934
- if (compareValue !== compareMode) {
935
- setCompareMode(compareValue);
1158
+ // Handle compare mode changes — always set to avoid stale-closure mismatch
1159
+ // (React skips re-render when the value is unchanged)
1160
+ setCompareMode(urlParams.get("compare") === "true");
1161
+ // Handle help panel changes
1162
+ const helpParam = urlParams.get("help");
1163
+ if (helpParam) {
1164
+ setHelpTerminalActiveTab("manual");
1165
+ setShowHelpTerminal(true);
1166
+ setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true"
1167
+ ? null
1168
+ : helpParam);
1169
+ }
1170
+ else {
1171
+ handleSetShowHelpTerminal(false);
936
1172
  }
937
- // Handle mode changes
1173
+ // Handle mode changes — always set to avoid stale-closure mismatch
938
1174
  const urlMode = urlParams.get("mode");
939
- if (urlMode && urlMode !== mode) {
1175
+ if (urlMode) {
940
1176
  setMode(urlMode);
941
1177
  }
942
1178
  // Handle fullscreen changes (shell-level state)
@@ -989,9 +1225,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
989
1225
  };
990
1226
  window.addEventListener("message", handleMessage);
991
1227
  window.addEventListener("popstate", handlePopState);
1228
+ window.addEventListener("visibilitychange", handleVisibilityChange);
992
1229
  return () => {
993
1230
  window.removeEventListener("message", handleMessage);
994
1231
  window.removeEventListener("popstate", handlePopState);
1232
+ window.removeEventListener("visibilitychange", handleVisibilityChange);
995
1233
  clearInterval(interval);
996
1234
  };
997
1235
  }, []);
@@ -1001,14 +1239,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1001
1239
  if (searchParams.get("noTour") !== null) {
1002
1240
  return;
1003
1241
  }
1242
+ // Don't start tour when there are setup errors (user is or will be on system status page)
1243
+ if (startupChecks.state === "complete" && startupChecks.hasBlockingIssues) {
1244
+ return;
1245
+ }
1246
+ // Don't start tour when already on settings system status page
1247
+ if (viewName === "settings" && searchParams.get("ccpanel") === "status") {
1248
+ return;
1249
+ }
1250
+ // Wait for startup checks so we know whether we'll redirect to status
1251
+ if (startupChecks.state !== "complete") {
1252
+ return;
1253
+ }
1004
1254
  const tour = configuration.activeTour;
1005
1255
  const key = tour === "default" ? "editor.tourShown" : "editor.tourShown." + tour;
1006
- const tourShown = localStorage.getItem(key);
1256
+ const tourShown = localStorageService.getString(key);
1007
1257
  if (!tourShown) {
1008
1258
  startTour();
1009
- localStorage.setItem(key, "true");
1259
+ localStorageService.setString(key, "true");
1010
1260
  }
1011
- }, [user]);
1261
+ }, [
1262
+ user,
1263
+ startupChecks.state,
1264
+ startupChecks.hasBlockingIssues,
1265
+ viewName,
1266
+ searchParams,
1267
+ configuration.activeTour,
1268
+ startTour,
1269
+ ]);
1012
1270
  // WebSocket initialization is performed after messageHandler is defined
1013
1271
  // Defer URL sync until loadItem is defined below
1014
1272
  // Mark end of initial load phase
@@ -1140,7 +1398,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1140
1398
  const loadComments = useCallback(async () => {
1141
1399
  if (!currentItemDescriptor)
1142
1400
  return;
1143
- const result = await getComments(currentItemDescriptor.id, currentItemDescriptor.language, currentItemDescriptor.version);
1401
+ const reviewId = searchParams.get("reviewId");
1402
+ const result = await getComments(currentItemDescriptor.id, currentItemDescriptor.language, currentItemDescriptor.version, reviewId ?? undefined);
1144
1403
  if (handleErrorResult(result, ui, state))
1145
1404
  return;
1146
1405
  setComments((x) => {
@@ -1153,7 +1412,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1153
1412
  allComments.sort((a, b) => a.position - b.position);
1154
1413
  return allComments;
1155
1414
  });
1156
- }, [currentItemDescriptor]);
1415
+ }, [currentItemDescriptor, searchParams]);
1157
1416
  // Assuming currentItemDescriptor, ui, state, handleErrorResult, and setSuggestedEdits
1158
1417
  // are available in your component context.
1159
1418
  const loadSuggestedEdits = useCallback(async () => {
@@ -1162,7 +1421,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1162
1421
  const result = await getSuggestedEdits(item.descriptor.id, item.descriptor.language, item.descriptor.version);
1163
1422
  if (handleErrorResult(result, ui, state))
1164
1423
  return;
1165
- setSuggestedEdits(result.data || []);
1424
+ const edits = result.data || [];
1425
+ setSuggestedEdits(edits);
1166
1426
  }, [item]);
1167
1427
  const loadFavorites = useCallback(async () => {
1168
1428
  try {
@@ -1236,6 +1496,25 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1236
1496
  return idB.localeCompare(idA);
1237
1497
  });
1238
1498
  }, []);
1499
+ const normalizeEditHistoryPayload = useCallback((value) => {
1500
+ if (Array.isArray(value)) {
1501
+ return value;
1502
+ }
1503
+ if (value && typeof value === "object") {
1504
+ const payload = value;
1505
+ const candidates = [
1506
+ payload.operations,
1507
+ payload.history,
1508
+ payload.items,
1509
+ payload.data,
1510
+ ];
1511
+ const firstArray = candidates.find((candidate) => Array.isArray(candidate));
1512
+ if (Array.isArray(firstArray)) {
1513
+ return firstArray;
1514
+ }
1515
+ }
1516
+ return [];
1517
+ }, []);
1239
1518
  useEffect(() => {
1240
1519
  // Read fresh page from the mutable slot context ref chain.
1241
1520
  // The slot context uses a stable ref that is mutated in-place (see editorSlotContext.ts),
@@ -1258,7 +1537,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1258
1537
  if (handleErrorResult(result, ui, state))
1259
1538
  return;
1260
1539
  setEditHistory((prev) => {
1261
- const next = result.data || [];
1540
+ const next = normalizeEditHistoryPayload(result.data);
1262
1541
  if (!prev.length)
1263
1542
  return sortEditHistoryByDateDesc(next);
1264
1543
  if (!next.length)
@@ -1317,6 +1596,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1317
1596
  const shouldFilterByLanguage = filterByLanguage !== undefined
1318
1597
  ? filterByLanguage
1319
1598
  : filterByCurrentLanguage;
1599
+ const trimmedHistoryQuery = historySearchQuery.trim();
1600
+ const historyQuery = trimmedHistoryQuery.length > 0 ? trimmedHistoryQuery : undefined;
1320
1601
  if (currentMode === "global") {
1321
1602
  // Global mode: optionally filter by session and/or language
1322
1603
  const currentLanguage = item?.descriptor?.language || currentItemDescriptor?.language;
@@ -1326,13 +1607,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1326
1607
  language: shouldFilterByLanguage && currentLanguage
1327
1608
  ? currentLanguage
1328
1609
  : undefined,
1610
+ query: historyQuery,
1329
1611
  });
1330
1612
  if (handleErrorResult(result, ui, state)) {
1331
1613
  console.error("[EditorShell] Failed to load history:", result);
1332
1614
  return;
1333
1615
  }
1334
1616
  setEditHistory((prev) => {
1335
- const next = result.data || [];
1617
+ const next = normalizeEditHistoryPayload(result.data);
1336
1618
  const scope = {
1337
1619
  mode: currentMode,
1338
1620
  filterBySession: shouldFilterBySession,
@@ -1343,8 +1625,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1343
1625
  };
1344
1626
  if (!prev.length)
1345
1627
  return sortEditHistoryByDateDesc(next.filter((op) => matchesHistoryScope(op, scope)));
1346
- if (!next.length)
1628
+ if (!next.length) {
1629
+ // When searching, respect the empty result — don't fall back to previous items
1630
+ if (historyQuery)
1631
+ return [];
1347
1632
  return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
1633
+ }
1348
1634
  const prevById = new Map(prev.map((x) => [x.id, x]));
1349
1635
  const nextById = new Set(next.map((x) => x.id));
1350
1636
  const merged = next
@@ -1380,9 +1666,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1380
1666
  return mergedOp;
1381
1667
  });
1382
1668
  // Preserve operations that arrived via WebSocket during the fetch window
1383
- for (const [id, priorOp] of prevById) {
1384
- if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1385
- merged.push(priorOp);
1669
+ // but not when filtering by search query — search results are authoritative
1670
+ if (!historyQuery) {
1671
+ for (const [id, priorOp] of prevById) {
1672
+ if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1673
+ merged.push(priorOp);
1674
+ }
1386
1675
  }
1387
1676
  }
1388
1677
  return sortEditHistoryByDateDesc(merged);
@@ -1405,12 +1694,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1405
1694
  const result = await getEditHistory({
1406
1695
  item: itemFilter,
1407
1696
  sessionId: shouldFilterBySession ? sessionId : undefined,
1697
+ query: historyQuery,
1408
1698
  });
1409
1699
  if (handleErrorResult(result, ui, state)) {
1410
1700
  console.error("[EditorShell] Failed to load item history:", result);
1411
1701
  return;
1412
1702
  }
1413
- let operations = result.data || [];
1703
+ let operations = normalizeEditHistoryPayload(result.data);
1414
1704
  // Client-side version filtering for current-version mode
1415
1705
  if (currentMode === "current-version") {
1416
1706
  // Defensive filter: only include operations for the current version
@@ -1433,8 +1723,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1433
1723
  };
1434
1724
  if (!prev.length)
1435
1725
  return sortEditHistoryByDateDesc(operations.filter((op) => matchesHistoryScope(op, scope)));
1436
- if (!operations.length)
1726
+ if (!operations.length) {
1727
+ if (historyQuery)
1728
+ return [];
1437
1729
  return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
1730
+ }
1438
1731
  const prevById = new Map(prev.map((x) => [x.id, x]));
1439
1732
  const nextById = new Set(operations.map((x) => x.id));
1440
1733
  const merged = operations
@@ -1470,9 +1763,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1470
1763
  return mergedOp;
1471
1764
  });
1472
1765
  // Preserve operations that arrived via WebSocket during the fetch window
1473
- for (const [id, priorOp] of prevById) {
1474
- if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1475
- merged.push(priorOp);
1766
+ // but not when filtering by search query — search results are authoritative
1767
+ if (!historyQuery) {
1768
+ for (const [id, priorOp] of prevById) {
1769
+ if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1770
+ merged.push(priorOp);
1771
+ }
1476
1772
  }
1477
1773
  }
1478
1774
  return sortEditHistoryByDateDesc(merged);
@@ -1483,9 +1779,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1483
1779
  historyMode,
1484
1780
  showOnlyMyChanges,
1485
1781
  filterByCurrentLanguage,
1782
+ historySearchQuery,
1486
1783
  item,
1487
1784
  currentItemDescriptor,
1488
1785
  matchesHistoryScope,
1786
+ normalizeEditHistoryPayload,
1489
1787
  sortEditHistoryByDateDesc,
1490
1788
  ]);
1491
1789
  // Debounced history refresh to avoid hammering `/parhelia/editHistory` on rapid UI changes.
@@ -1551,13 +1849,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1551
1849
  }
1552
1850
  }
1553
1851
  else if (historyMode !== "global" && currentItemDescriptor) {
1852
+ // Always load immediately for page-centric/current-version/timeline so we don't show
1853
+ // an empty history list for the debounce window (e.g. 600ms). The effect only runs on
1854
+ // mode or item id/lang/version change, not on every keystroke, so this avoids flaky
1855
+ // undo/redo tests and improves UX when switching to page-centric.
1554
1856
  if (!historyInitialLoadDoneRef.current) {
1555
1857
  historyInitialLoadDoneRef.current = true;
1556
- refreshHistoryRef.current(historyMode);
1557
- }
1558
- else {
1559
- debouncedRefreshHistoryRef.current(historyMode);
1560
1858
  }
1859
+ refreshHistoryRef.current(historyMode);
1561
1860
  }
1562
1861
  else if (historyMode !== "global" && !currentItemDescriptor) {
1563
1862
  // Clear history if no item loaded in page-centric modes
@@ -1570,6 +1869,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1570
1869
  historyMode,
1571
1870
  showOnlyMyChanges,
1572
1871
  filterByCurrentLanguage,
1872
+ historySearchQuery,
1573
1873
  currentItemDescriptor?.id,
1574
1874
  currentItemDescriptor?.language,
1575
1875
  currentItemDescriptor?.version,
@@ -1634,6 +1934,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1634
1934
  if (isInitialLoad)
1635
1935
  return;
1636
1936
  const current = new URLSearchParams(window.location.search);
1937
+ const urlWorkspace = current.get("workspace");
1938
+ const urlView = current.get("view");
1939
+ const isWorkspaceTransitioning = !!urlWorkspace && urlWorkspace !== viewName;
1637
1940
  // Sync item-related parameters only when an item is selected
1638
1941
  if (currentItemDescriptor) {
1639
1942
  if (current.get("itemid") !== currentItemDescriptor.id) {
@@ -1664,10 +1967,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1664
1967
  // If reviewId or urlItemId exists, preserve itemid/lang/version from URL
1665
1968
  }
1666
1969
  // Always sync workspace-related parameters regardless of item selection
1667
- if (current.get("workspace") !== viewName) {
1970
+ if (!isWorkspaceTransitioning && current.get("workspace") !== viewName) {
1668
1971
  current.set("workspace", viewName);
1669
1972
  }
1670
- current.delete("view"); // Remove legacy view param
1671
1973
  // Sync sidebar state
1672
1974
  const currentSidebars = current.get("sidebar") ?? "";
1673
1975
  const newSidebars = openSidebars.join(",");
@@ -1679,6 +1981,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1679
1981
  current.delete("sidebar");
1680
1982
  }
1681
1983
  }
1984
+ if (showHelpTerminal) {
1985
+ current.set("help", selectedHelpSectionId ?? "contents");
1986
+ }
1987
+ else {
1988
+ current.delete("help");
1989
+ }
1682
1990
  if (!compareMode) {
1683
1991
  current.delete("compare");
1684
1992
  current.delete("compareLanguage");
@@ -1703,15 +2011,15 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1703
2011
  else {
1704
2012
  current.delete("wizardid");
1705
2013
  }
1706
- // Only preserve ccpanel parameter when on settings view, remove it otherwise
1707
- if (viewName === "settings") {
1708
- const ccpanel = current.get("ccpanel");
1709
- if (ccpanel) {
1710
- current.set("ccpanel", ccpanel);
1711
- }
1712
- }
1713
- else {
2014
+ // Preserve settings-specific parameters while transitioning into Settings too.
2015
+ // Some callers update the URL first and switch the workspace state a moment later.
2016
+ const isSettingsNavigation = viewName === "settings" ||
2017
+ urlWorkspace === "settings" ||
2018
+ urlView === "settings";
2019
+ if (!isSettingsNavigation) {
1714
2020
  current.delete("ccpanel");
2021
+ current.delete("providerId");
2022
+ current.delete("modelId");
1715
2023
  }
1716
2024
  // Preserve reviewId parameter if it exists (for review links)
1717
2025
  // This ensures review links don't lose the reviewId when URL is synced
@@ -1724,17 +2032,42 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1724
2032
  const browserPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
1725
2033
  const newUrl = `${browserPathname}?${current.toString()}`;
1726
2034
  const oldUrl = `${browserPathname}${window.location.search}`;
2035
+ const isRestoringLastSyncedUrl = newUrl === lastUrlRef.current;
1727
2036
  // Skip pushing to history if we're handling a popstate event (browser back/forward)
1728
2037
  // This prevents the URL sync from overwriting the history during back navigation
1729
2038
  if (isHandlingPopStateRef.current) {
1730
2039
  return;
1731
2040
  }
1732
2041
  if (newUrl !== oldUrl) {
1733
- window.history.pushState(null, "", newUrl);
1734
- // Update lastUrlRef so the popstate handler knows the current URL
1735
- // This fixes the issue where goBack() wouldn't work because lastUrl was stale
2042
+ if (isFirstUrlSyncRef.current ||
2043
+ switchWorkspacePushedRef.current ||
2044
+ isRestoringLastSyncedUrl) {
2045
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
2046
+ isFirstUrlSyncRef.current = false;
2047
+ switchWorkspacePushedRef.current = false;
2048
+ }
2049
+ else {
2050
+ window.history.pushState(getCurrentHistoryState(), "", newUrl);
2051
+ }
1736
2052
  lastUrlRef.current = newUrl;
1737
2053
  }
2054
+ else {
2055
+ // Even when the first sync is a no-op (URL already matches state), consume
2056
+ // the first-sync flag. Otherwise the *next* real change (e.g. user selecting
2057
+ // a different item) would hit the replaceState branch and overwrite the
2058
+ // landing history entry — eating the "back" target and making browser back
2059
+ // skip past the initial page.
2060
+ if (isFirstUrlSyncRef.current) {
2061
+ isFirstUrlSyncRef.current = false;
2062
+ lastUrlRef.current = newUrl;
2063
+ }
2064
+ if (switchWorkspacePushedRef.current) {
2065
+ // A workspace change may already have pushed the exact target URL. If we leave
2066
+ // this flag set, the next unrelated item navigation will incorrectly replace
2067
+ // history instead of pushing a new entry, which breaks browser back/forward.
2068
+ switchWorkspacePushedRef.current = false;
2069
+ }
2070
+ }
1738
2071
  }, [
1739
2072
  currentItemDescriptor,
1740
2073
  viewName,
@@ -1746,6 +2079,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1746
2079
  fullscreen,
1747
2080
  currentWizardId,
1748
2081
  openSidebars,
2082
+ showHelpTerminal,
2083
+ selectedHelpSectionId,
1749
2084
  ]);
1750
2085
  useEffect(() => {
1751
2086
  async function load() {
@@ -1842,12 +2177,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1842
2177
  "en",
1843
2178
  version: 0,
1844
2179
  };
1845
- const loadedItem = await itemsRepository.getItem(itemToLoad);
1846
- if (!loadedItem) {
1847
- return undefined;
1848
- }
1849
2180
  // ensure this is object has no additional properties
1850
2181
  itemToLoad = getItemDescriptor(itemToLoad);
2182
+ const requestedItemKey = makeItemKey(itemToLoad);
1851
2183
  // Check if item is already open in any slot - if so, reuse that slot instead of creating a new one
1852
2184
  const existingSlotForItem = editorSlots.find((s) => s.itemDescriptor.id === itemToLoad.id &&
1853
2185
  s.itemDescriptor.language === itemToLoad.language &&
@@ -1858,6 +2190,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1858
2190
  !options?.openInNewSlot &&
1859
2191
  !options?.targetSlotId) {
1860
2192
  const isExistingSlotActive = existingSlotForItem.slotId === activeSlotIdRef.current;
2193
+ if (isExistingSlotActive) {
2194
+ latestGlobalLoadKeyRef.current = requestedItemKey;
2195
+ }
2196
+ const loadedItem = await itemsRepository.getItem(itemToLoad);
2197
+ if (!loadedItem) {
2198
+ return undefined;
2199
+ }
2200
+ if (isExistingSlotActive &&
2201
+ latestGlobalLoadKeyRef.current !== requestedItemKey) {
2202
+ return loadedItem;
2203
+ }
1861
2204
  // If forceRefresh is true, update the slot with a new refreshToken to trigger a reload
1862
2205
  if (options?.forceRefresh) {
1863
2206
  setEditorSlots((prev) => {
@@ -1932,6 +2275,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1932
2275
  // - If the slot is active (or is becoming active as the first slot), do update.
1933
2276
  const isFirstSlot = editorSlots.length === 0;
1934
2277
  const shouldUpdateGlobalCurrentItem = isFirstSlot || targetSlotId === activeSlotIdRef.current;
2278
+ if (shouldUpdateGlobalCurrentItem) {
2279
+ latestGlobalLoadKeyRef.current = requestedItemKey;
2280
+ }
2281
+ const loadedItem = await itemsRepository.getItem(itemToLoad);
2282
+ if (!loadedItem) {
2283
+ return undefined;
2284
+ }
2285
+ if (shouldUpdateGlobalCurrentItem &&
2286
+ latestGlobalLoadKeyRef.current !== requestedItemKey) {
2287
+ return loadedItem;
2288
+ }
1935
2289
  // Active slot is controlled ONLY by mouse hover - only activate the first slot on initial load
1936
2290
  if (isFirstSlot) {
1937
2291
  activeSlotIdRef.current = targetSlotId;
@@ -2039,9 +2393,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2039
2393
  useEffect(() => {
2040
2394
  if (fullscreen &&
2041
2395
  !searchParams.get("fullscreen") &&
2042
- !configuration.forceFullscreen)
2396
+ !configuration.forceFullscreen &&
2397
+ !isMobile)
2043
2398
  setShowFullscreenHint(true);
2044
- }, [fullscreen, configuration.forceFullscreen, searchParams]);
2399
+ }, [fullscreen, configuration.forceFullscreen, searchParams, isMobile]);
2045
2400
  const state = {
2046
2401
  page,
2047
2402
  configuration,
@@ -2140,20 +2495,35 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2140
2495
  ? existingOp.progress
2141
2496
  : op.progress;
2142
2497
  // IMPORTANT: Once canUndo becomes true, never downgrade it to false,
2143
- // UNLESS this update indicates the operation was undone. When an undo
2144
- // completes, the server sends canUndo: false and canRedo: true (or undone: true).
2145
- // We must respect that transition so the UI reflects the undo.
2146
- const isUndoUpdate = op.undone === true || op.canRedo === true;
2498
+ // UNLESS this update indicates the operation was undone.
2499
+ // Runtime logs show long-running undo completion can arrive as:
2500
+ // - existing: canUndo=true, executionStatus=executing
2501
+ // - incoming: canUndo=false, executionStatus=completed
2502
+ // without explicit undone/canRedo flags yet.
2503
+ const isExplicitUndoUpdate = op.undone === true || op.canRedo === true;
2504
+ const isInferredUndoCompletion = existingOp.executionStatus === "executing" &&
2505
+ op.executionStatus === "completed" &&
2506
+ existingOp.canUndo === true &&
2507
+ op.canUndo === false;
2508
+ const isUndoUpdate = isExplicitUndoUpdate || isInferredUndoCompletion;
2147
2509
  const mergedCanUndo = isUndoUpdate
2148
2510
  ? false
2149
2511
  : existingOp.canUndo === true
2150
2512
  ? true
2151
2513
  : op.canUndo;
2514
+ const mergedCanRedo = isUndoUpdate
2515
+ ? true
2516
+ : op.canRedo ?? existingOp.canRedo;
2517
+ const mergedUndone = isUndoUpdate
2518
+ ? true
2519
+ : op.undone ?? existingOp.undone;
2152
2520
  const mergedOp = {
2153
2521
  ...existingOp,
2154
2522
  ...op,
2155
2523
  progress: mergedProgress,
2156
2524
  canUndo: mergedCanUndo,
2525
+ canRedo: mergedCanRedo,
2526
+ undone: mergedUndone,
2157
2527
  };
2158
2528
  // Ensure undone operations always have canRedo: true.
2159
2529
  if (mergedOp.undone && !mergedOp.canRedo) {
@@ -2207,10 +2577,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2207
2577
  });
2208
2578
  // Ref for markOperationComplete callback (needed because operationsContext is created later)
2209
2579
  const markOperationCompleteRef = useRef(null);
2210
- // When the websocket is reconnecting, we can briefly lose the ability to send messages.
2211
- // Agent dialog responses (e.g. questionnaire cancel/submit) must not be dropped, otherwise
2212
- // the backend tool call can remain pending and tests/users will hang.
2213
- const pendingAgentDialogResponsesRef = useRef([]);
2214
2580
  // WebSocket message handler and connection
2215
2581
  const messageHandler = useSocketMessageHandler({
2216
2582
  sessionId,
@@ -2236,28 +2602,31 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2236
2602
  markOperationCompleteRef.current?.(operationId);
2237
2603
  },
2238
2604
  });
2605
+ // Concurrent user limit error state
2606
+ const [concurrentUserLimitError, setConcurrentUserLimitError] = useState(null);
2607
+ concurrentUserLimitErrorRef.current = concurrentUserLimitError;
2608
+ const handleRetryConnection = useCallback(() => {
2609
+ setConcurrentUserLimitError(null);
2610
+ // Force reconnection by triggering a new connection attempt
2611
+ // The useEditorWebSocket hook will check availability again
2612
+ const socket = globalThis.editorSocket;
2613
+ if (socket) {
2614
+ socket.close();
2615
+ delete globalThis.editorSocket;
2616
+ }
2617
+ // The hook will automatically attempt to reconnect
2618
+ }, []);
2239
2619
  const { socketRef: socketInstanceRef } = useEditorWebSocket({
2240
2620
  sessionId,
2241
2621
  onMessage: messageHandler,
2242
2622
  onOpen: async () => {
2623
+ // Clear concurrent user limit error on successful connection
2624
+ setConcurrentUserLimitError(null);
2625
+ // Startup WebSocket probe may have failed with a blocking error while seats were full;
2626
+ // re-run status checks quietly so "1 error" does not stick after a successful connect.
2627
+ void startupChecks.recheckQuiet();
2243
2628
  // Increment socket connection version to trigger re-subscriptions
2244
2629
  setSocketConnectionVersion((v) => v + 1);
2245
- // Flush any queued agent dialog responses now that the socket is open.
2246
- // Keep this early so pending tool calls unblock ASAP.
2247
- try {
2248
- if (socketInstanceRef.current &&
2249
- socketInstanceRef.current.readyState === WebSocket.OPEN &&
2250
- pendingAgentDialogResponsesRef.current.length > 0) {
2251
- // FIFO flush
2252
- while (pendingAgentDialogResponsesRef.current.length > 0) {
2253
- const queued = pendingAgentDialogResponsesRef.current.shift();
2254
- socketInstanceRef.current.send(JSON.stringify(queued));
2255
- }
2256
- }
2257
- }
2258
- catch (e) {
2259
- console.error("Failed to flush queued agent dialog responses:", e);
2260
- }
2261
2630
  // Fetch any running operations on (re)connect for auto-resume
2262
2631
  // This ensures the UI shows operations that are still executing
2263
2632
  try {
@@ -2273,24 +2642,37 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2273
2642
  }
2274
2643
  },
2275
2644
  onError: (error) => console.error("WebSocket error:", error),
2645
+ onConcurrentUserLimit: (error) => {
2646
+ setConcurrentUserLimitError(error);
2647
+ },
2648
+ onSessionRevoked: () => promptSessionReconnect("session-revoked"),
2276
2649
  connectSocket,
2277
2650
  requestQuota,
2278
2651
  sendClientInfo,
2652
+ setSocketDiagnostics,
2279
2653
  });
2654
+ useEffect(() => {
2655
+ const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
2656
+ if (hasMySession &&
2657
+ socketInstanceRef.current?.readyState === WebSocket.OPEN) {
2658
+ toast.dismiss("session-revoked");
2659
+ }
2660
+ }, [activeSessions, sessionId, socketConnectionVersion, socketInstanceRef]);
2661
+ useEffect(() => {
2662
+ const handleRevoked = (event) => {
2663
+ const customEvent = event;
2664
+ promptSessionReconnect(customEvent?.detail?.reason);
2665
+ };
2666
+ window.addEventListener("parhelia:session-revoked", handleRevoked);
2667
+ return () => {
2668
+ window.removeEventListener("parhelia:session-revoked", handleRevoked);
2669
+ };
2670
+ }, [promptSessionReconnect]);
2280
2671
  const sendSocketMessage = useCallback((message) => {
2281
2672
  if (socketInstanceRef.current &&
2282
2673
  socketInstanceRef.current.readyState === WebSocket.OPEN) {
2283
2674
  socketInstanceRef.current.send(JSON.stringify(message));
2284
2675
  }
2285
- else if (message.type === "agent-dialog-response") {
2286
- // Queue dialog responses to avoid losing them during reconnects.
2287
- pendingAgentDialogResponsesRef.current.push(message);
2288
- // Prevent unbounded growth in pathological scenarios.
2289
- if (pendingAgentDialogResponsesRef.current.length > 50) {
2290
- pendingAgentDialogResponsesRef.current =
2291
- pendingAgentDialogResponsesRef.current.slice(-50);
2292
- }
2293
- }
2294
2676
  }, [socketInstanceRef]);
2295
2677
  // URL update helper - defined early so it can be used by workspace/sidebar functions
2296
2678
  const updateUrl = useCallback((params) => {
@@ -2309,7 +2691,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2309
2691
  ? `${browserPathname}?${queryString}`
2310
2692
  : browserPathname;
2311
2693
  if (typeof window !== "undefined") {
2312
- window.history.pushState(null, "", newUrl);
2694
+ window.history.pushState(getCurrentHistoryState(), "", newUrl);
2695
+ lastUrlRef.current = newUrl;
2313
2696
  }
2314
2697
  else {
2315
2698
  router.push(newUrl, { scroll: false });
@@ -2334,8 +2717,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2334
2717
  if (!options?.skipNavigationHistory) {
2335
2718
  addNavigationEntry(targetWorkspaceId, item);
2336
2719
  }
2337
- // Update URL
2338
- updateUrl({ workspace: targetWorkspaceId });
2720
+ // Mark that we're pushing from switchWorkspace so the URL sync effect
2721
+ // will replaceState instead of pushing a second history entry.
2722
+ switchWorkspacePushedRef.current = true;
2723
+ updateUrl({
2724
+ workspace: targetWorkspaceId,
2725
+ ...options?.urlParams,
2726
+ });
2339
2727
  if (typeof document.startViewTransition === "function") {
2340
2728
  document.startViewTransition(() => {
2341
2729
  flushSync(() => {
@@ -2355,14 +2743,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2355
2743
  updateUrl,
2356
2744
  handleSetShowAgentsPanel,
2357
2745
  ]);
2358
- // Legacy alias for backwards compatibility
2359
- const switchView = useCallback((viewName, options) => {
2360
- // Handle ccpanel for settings workspace
2361
- if (options?.ccpanel) {
2362
- updateUrl({ ccpanel: options.ccpanel, workspace: viewName });
2363
- }
2364
- switchWorkspace(viewName, options);
2365
- }, [switchWorkspace, updateUrl]);
2366
2746
  // Helper: get all sidebar IDs that should be preserved (locked sidebars + their stack mates)
2367
2747
  const getPreservedSidebarIds = useCallback(() => {
2368
2748
  const lockedSet = new Set(lockedSidebarsRef.current);
@@ -2400,6 +2780,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2400
2780
  setOpenSidebars(newSidebars);
2401
2781
  setLockedSidebars((locked) => locked.filter((id) => id !== sidebarId));
2402
2782
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2783
+ if (sidebarId === "agents-panel") {
2784
+ setUserPreferences({ showAgentsPanel: false });
2785
+ }
2403
2786
  startTransition(() => {
2404
2787
  updateUrl({ sidebar: newSidebars.join(",") || undefined });
2405
2788
  });
@@ -2414,34 +2797,69 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2414
2797
  ];
2415
2798
  openSidebarsRef.current = newSidebars;
2416
2799
  setOpenSidebars(newSidebars);
2800
+ ensureSidebarPinned(sidebarId);
2417
2801
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2802
+ if (sidebarId === "agents-panel") {
2803
+ setUserPreferences({ showAgentsPanel: true });
2804
+ }
2805
+ // On mobile, close the editor form panel when opening a sidebar
2806
+ if (isMobile) {
2807
+ setMobileEditorPanelOpenRaw(false);
2808
+ }
2418
2809
  startTransition(() => {
2419
2810
  updateUrl({ sidebar: newSidebars.join(",") || undefined });
2420
2811
  });
2421
- }, [updateUrl, getPreservedSidebarIds, normalizeSidebarStacks]);
2812
+ }, [
2813
+ updateUrl,
2814
+ getPreservedSidebarIds,
2815
+ normalizeSidebarStacks,
2816
+ ensureSidebarPinned,
2817
+ setUserPreferences,
2818
+ isMobile,
2819
+ ]);
2422
2820
  // Ensure a sidebar is open (without toggling it closed if already open)
2423
- const openSidebar = useCallback((sidebarId) => {
2821
+ const openSidebar = useCallback((sidebarId, options) => {
2424
2822
  const currentOpenSidebars = openSidebarsRef.current;
2425
2823
  if (currentOpenSidebars.includes(sidebarId)) {
2426
2824
  // Already open, nothing to do
2427
2825
  return;
2428
2826
  }
2429
- // Keep sidebars that are locked OR in a stack with a locked sidebar
2430
- const preservedSet = getPreservedSidebarIds();
2431
- const newSidebars = [
2432
- ...currentOpenSidebars.filter((id) => preservedSet.has(id)), // Keep locked stacks
2433
- sidebarId, // Add the new one
2434
- ];
2827
+ const preservedSet = options?.preserveOpenSidebars
2828
+ ? undefined
2829
+ : getPreservedSidebarIds();
2830
+ const newSidebars = options?.preserveOpenSidebars
2831
+ ? [...currentOpenSidebars, sidebarId]
2832
+ : [
2833
+ ...currentOpenSidebars.filter((id) => preservedSet?.has(id)),
2834
+ sidebarId,
2835
+ ];
2435
2836
  openSidebarsRef.current = newSidebars;
2436
2837
  setOpenSidebars(newSidebars);
2838
+ ensureSidebarPinned(sidebarId);
2437
2839
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2840
+ if (sidebarId === "agents-panel") {
2841
+ setUserPreferences({ showAgentsPanel: true });
2842
+ }
2843
+ // On mobile, close the editor form panel when opening a sidebar
2844
+ if (isMobile) {
2845
+ setMobileEditorPanelOpenRaw(false);
2846
+ }
2438
2847
  startTransition(() => {
2439
2848
  updateUrl({ sidebar: newSidebars.join(",") || undefined });
2440
2849
  });
2441
- }, [updateUrl, getPreservedSidebarIds, normalizeSidebarStacks]);
2850
+ }, [
2851
+ updateUrl,
2852
+ getPreservedSidebarIds,
2853
+ normalizeSidebarStacks,
2854
+ ensureSidebarPinned,
2855
+ setUserPreferences,
2856
+ isMobile,
2857
+ ]);
2442
2858
  // Toggle lock state for a sidebar stack (keeps it visible when selecting another)
2443
2859
  // When toggling any sidebar, we toggle the entire stack it belongs to
2444
2860
  const toggleSidebarLock = useCallback((sidebarId) => {
2861
+ if (isMobile)
2862
+ return;
2445
2863
  const currentStacks = sidebarStacksRef.current;
2446
2864
  const stackContainingSidebar = currentStacks.find((stack) => stack.includes(sidebarId));
2447
2865
  const sidebarIdsToToggle = stackContainingSidebar || [sidebarId];
@@ -2459,7 +2877,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2459
2877
  ];
2460
2878
  }
2461
2879
  });
2462
- }, []);
2880
+ }, [isMobile]);
2463
2881
  const stackSidebar = useCallback((sidebarId, targetSidebarId, position = "after") => {
2464
2882
  if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId)
2465
2883
  return;
@@ -2491,6 +2909,38 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2491
2909
  return normalizeSidebarStacks(openIds, next);
2492
2910
  });
2493
2911
  }, [normalizeSidebarStacks]);
2912
+ const moveSidebarToColumn = useCallback((sidebarId, targetSidebarId, position = "after") => {
2913
+ if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId) {
2914
+ return;
2915
+ }
2916
+ const currentOpen = openSidebarsRef.current;
2917
+ const baseOpen = currentOpen.filter((id) => id !== sidebarId);
2918
+ const targetIndex = baseOpen.indexOf(targetSidebarId);
2919
+ if (targetIndex === -1) {
2920
+ return;
2921
+ }
2922
+ const insertIndex = position === "before" ? targetIndex : targetIndex + 1;
2923
+ const nextOpen = [...baseOpen];
2924
+ nextOpen.splice(insertIndex, 0, sidebarId);
2925
+ openSidebarsRef.current = nextOpen;
2926
+ setOpenSidebars(nextOpen);
2927
+ startTransition(() => {
2928
+ updateUrl({ sidebar: nextOpen.join(",") || undefined });
2929
+ });
2930
+ setSidebarStacks((prev) => {
2931
+ const normalized = normalizeSidebarStacks(nextOpen, prev);
2932
+ const next = normalized
2933
+ .map((stack) => stack.filter((id) => id !== sidebarId))
2934
+ .filter((stack) => stack.length > 0);
2935
+ const targetStackIndex = next.findIndex((stack) => stack.includes(targetSidebarId));
2936
+ if (targetStackIndex === -1) {
2937
+ next.push([sidebarId]);
2938
+ return normalizeSidebarStacks(nextOpen, next);
2939
+ }
2940
+ next.splice(position === "before" ? targetStackIndex : targetStackIndex + 1, 0, [sidebarId]);
2941
+ return normalizeSidebarStacks(nextOpen, next);
2942
+ });
2943
+ }, [normalizeSidebarStacks, updateUrl]);
2494
2944
  const unstackSidebar = useCallback((sidebarId) => {
2495
2945
  setSidebarStacks((prev) => {
2496
2946
  const openIds = openSidebarsRef.current;
@@ -2536,8 +2986,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2536
2986
  // Get resolved sidebar (with panels materialized)
2537
2987
  // Note: This is defined as a function that will be called later when editContext is available
2538
2988
  const sidebars = configuration.editor.sidebars ?? [];
2989
+ const taskboardSidebarIds = new Set([
2990
+ "taskboard-project-list",
2991
+ "taskboard-my-tasks",
2992
+ ]);
2993
+ const getSidebarsForWorkspace = useCallback((targetWorkspaceId) => {
2994
+ const isTaskboardWorkspace = targetWorkspaceId === "taskboard";
2995
+ const workspaceAllowedSidebarIds = userInfo.workspaces?.find((w) => w.id === targetWorkspaceId)
2996
+ ?.sidebars ?? [];
2997
+ return sidebars.filter((s) => {
2998
+ const isTaskboardSidebar = taskboardSidebarIds.has(s.id);
2999
+ if (isTaskboardWorkspace && !isTaskboardSidebar)
3000
+ return false;
3001
+ if (!isTaskboardWorkspace && isTaskboardSidebar)
3002
+ return false;
3003
+ // Always show agents-panel regardless of workspace settings.
3004
+ if (s.id === "agents-panel") {
3005
+ return true;
3006
+ }
3007
+ // If no workspace settings or no sidebars defined for current workspace, show all.
3008
+ if (workspaceAllowedSidebarIds.length === 0) {
3009
+ return true;
3010
+ }
3011
+ // Only show sidebars that are in the allowed list for the current workspace.
3012
+ return workspaceAllowedSidebarIds.includes(s.id);
3013
+ });
3014
+ }, [sidebars, userInfo.workspaces]);
2539
3015
  const getResolvedSidebar = useCallback((sidebarId) => {
2540
- const sidebar = sidebars.find((s) => s.id === sidebarId);
3016
+ const sidebar = getSidebarsForWorkspace(workspaceId).find((s) => s.id === sidebarId);
2541
3017
  if (!sidebar)
2542
3018
  return undefined;
2543
3019
  // Resolve panel factories using editContextRef to avoid circular dependency
@@ -2551,7 +3027,35 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2551
3027
  ...sidebar,
2552
3028
  panels: resolvedPanels,
2553
3029
  };
2554
- }, [sidebars]);
3030
+ }, [getSidebarsForWorkspace, workspaceId]);
3031
+ useEffect(() => {
3032
+ if (!currentWorkspace.supportsSidebars) {
3033
+ return;
3034
+ }
3035
+ const allowedIds = new Set(getSidebarsForWorkspace(workspaceId).map((sidebar) => sidebar.id));
3036
+ const currentOpen = openSidebarsRef.current;
3037
+ let nextOpen = currentOpen.filter((id) => allowedIds.has(id));
3038
+ if (nextOpen.length === 0 && currentWorkspace.defaultSidebars?.length) {
3039
+ nextOpen = currentWorkspace.defaultSidebars.filter((id) => allowedIds.has(id));
3040
+ }
3041
+ const unchanged = nextOpen.length === currentOpen.length &&
3042
+ nextOpen.every((id, index) => id === currentOpen[index]);
3043
+ if (unchanged) {
3044
+ return;
3045
+ }
3046
+ openSidebarsRef.current = nextOpen;
3047
+ setOpenSidebars(nextOpen);
3048
+ setSidebarStacks((prev) => normalizeSidebarStacks(nextOpen, prev));
3049
+ startTransition(() => {
3050
+ updateUrl({ sidebar: nextOpen.join(",") || undefined });
3051
+ });
3052
+ }, [
3053
+ currentWorkspace,
3054
+ getSidebarsForWorkspace,
3055
+ normalizeSidebarStacks,
3056
+ updateUrl,
3057
+ workspaceId,
3058
+ ]);
2555
3059
  // Listen for switch-workspace and open-sidebar commands from agents via websocket
2556
3060
  useEffect(() => {
2557
3061
  const handleAgentMessage = (message) => {
@@ -2765,9 +3269,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2765
3269
  }
2766
3270
  return true;
2767
3271
  }, [operations, ignoreBlur, sessionId]);
3272
+ const quickSwitcherEntries = useMemo(() => {
3273
+ const entries = [];
3274
+ const seen = new Set();
3275
+ const pushEntry = (entry) => {
3276
+ const key = entry.item
3277
+ ? `${entry.workspaceId}:${entry.item.id}:${entry.item.language}:${entry.item.version}`
3278
+ : `${entry.workspaceId}:no-item`;
3279
+ if (seen.has(key))
3280
+ return;
3281
+ seen.add(key);
3282
+ entries.push(entry);
3283
+ };
3284
+ for (const entry of navigationHistory) {
3285
+ pushEntry(entry);
3286
+ }
3287
+ for (const entry of browseHistory) {
3288
+ if (!entry.id || !entry.language)
3289
+ continue;
3290
+ pushEntry({
3291
+ workspaceId,
3292
+ item: {
3293
+ id: entry.id,
3294
+ language: entry.language,
3295
+ version: entry.version ?? 0,
3296
+ },
3297
+ timestamp: entry.visitedAt
3298
+ ? new Date(entry.visitedAt).getTime()
3299
+ : Date.now(),
3300
+ displayName: entry.name || workspaceId,
3301
+ itemName: entry.name,
3302
+ itemPath: entry.path,
3303
+ itemIcon: entry.icon,
3304
+ });
3305
+ }
3306
+ return entries.slice(0, 25);
3307
+ }, [navigationHistory, browseHistory, workspaceId]);
2768
3308
  // Quick switcher handlers
2769
3309
  const showQuickSwitcher = useCallback((show) => {
2770
- if (show && navigationHistory.length > 1) {
3310
+ if (show && quickSwitcherEntries.length > 1) {
2771
3311
  setQuickSwitcherVisible(true);
2772
3312
  // Start with index 1 (second entry - previous entry) for quick switching
2773
3313
  setQuickSwitcherSelectedIndex(1);
@@ -2775,11 +3315,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2775
3315
  else {
2776
3316
  setQuickSwitcherVisible(false);
2777
3317
  }
2778
- }, [navigationHistory]);
3318
+ }, [quickSwitcherEntries]);
2779
3319
  const cycleQuickSwitcher = useCallback((direction) => {
2780
3320
  if (!quickSwitcherVisible)
2781
3321
  return;
2782
- const maxItems = Math.min(5, navigationHistory.length);
3322
+ const maxItems = Math.min(5, quickSwitcherEntries.length);
2783
3323
  setQuickSwitcherSelectedIndex((current) => {
2784
3324
  let newIndex = current;
2785
3325
  // Determine grid layout (responsive columns)
@@ -2824,9 +3364,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2824
3364
  }
2825
3365
  return newIndex;
2826
3366
  });
2827
- }, [quickSwitcherVisible, navigationHistory]);
3367
+ }, [quickSwitcherVisible, quickSwitcherEntries]);
2828
3368
  const handleQuickSwitcherSelect = useCallback((index) => {
2829
- const selectedEntry = navigationHistory[index];
3369
+ const selectedEntry = quickSwitcherEntries[index];
2830
3370
  if (selectedEntry) {
2831
3371
  // Determine target workspace: entries with items should go to "editor" workspace
2832
3372
  // (fixes issue where browse history entries initialized with wrong workspaceId)
@@ -2878,23 +3418,46 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2878
3418
  }
2879
3419
  setQuickSwitcherVisible(false);
2880
3420
  }, [
2881
- navigationHistory,
3421
+ quickSwitcherEntries,
2882
3422
  loadItem,
2883
3423
  switchWorkspace,
2884
3424
  workspaceId,
2885
3425
  item,
2886
3426
  setNavigationHistory,
2887
3427
  ]);
3428
+ useEffect(() => {
3429
+ if (typeof window === "undefined" ||
3430
+ process.env.PARHELIA_DEV_MODE !== "true") {
3431
+ return;
3432
+ }
3433
+ window.__parheliaQuickSwitcherTestApi = {
3434
+ open: () => showQuickSwitcher(true),
3435
+ cycle: (direction = "next") => cycleQuickSwitcher(direction),
3436
+ closeWithoutSelection: () => setQuickSwitcherVisible(false),
3437
+ selectCurrent: () => handleQuickSwitcherSelect(quickSwitcherSelectedIndex),
3438
+ getState: () => ({
3439
+ visible: quickSwitcherVisible,
3440
+ selectedIndex: quickSwitcherSelectedIndex,
3441
+ entryCount: quickSwitcherEntries.length,
3442
+ entries: quickSwitcherEntries.map((entry) => ({
3443
+ workspaceId: entry.workspaceId,
3444
+ itemId: entry.item?.id,
3445
+ displayName: entry.displayName,
3446
+ })),
3447
+ }),
3448
+ };
3449
+ return () => {
3450
+ delete window.__parheliaQuickSwitcherTestApi;
3451
+ };
3452
+ }, [
3453
+ showQuickSwitcher,
3454
+ cycleQuickSwitcher,
3455
+ handleQuickSwitcherSelect,
3456
+ quickSwitcherSelectedIndex,
3457
+ ]);
2888
3458
  const { handleKeyDown } = useKeyboardNavigation({
2889
3459
  editContextRef,
2890
- operations,
2891
- pageViewContext: activePageViewContext,
2892
- configuration,
2893
- item,
2894
- browseHistory,
2895
- loadItem,
2896
- showInfoToast,
2897
- showErrorToast,
3460
+ keyboardCommands: activeKeyboardCommands,
2898
3461
  executeCommand,
2899
3462
  showQuickSwitcher,
2900
3463
  cycleQuickSwitcher,
@@ -3014,10 +3577,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3014
3577
  }
3015
3578
  return null;
3016
3579
  };
3580
+ let modifierWasPressedOnMouseDown = false;
3581
+ const handleMouseDown = (event) => {
3582
+ modifierWasPressedOnMouseDown = event.ctrlKey || event.metaKey;
3583
+ };
3017
3584
  const handleCtrlClick = async (event) => {
3018
- // Only proceed if Ctrl (or Cmd on Mac) is pressed
3585
+ // Only proceed if Ctrl/Cmd was already pressed when the mouse interaction started.
3586
+ // This avoids accidental navigation when users press Ctrl after selecting text to copy.
3587
+ if (!modifierWasPressedOnMouseDown)
3588
+ return;
3589
+ // Also require the modifier to still be held for the final click event.
3019
3590
  if (!event.ctrlKey && !event.metaKey)
3020
3591
  return;
3592
+ modifierWasPressedOnMouseDown = false;
3021
3593
  const target = event.target;
3022
3594
  const text = getTextFromElement(target);
3023
3595
  if (text && isGuid(text)) {
@@ -3032,8 +3604,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3032
3604
  skipViewChange: true,
3033
3605
  });
3034
3606
  if (item) {
3035
- // Switch to the editor view
3036
- switchView("editor", {
3607
+ switchWorkspace("editor", {
3037
3608
  skipNavigationHistory: true,
3038
3609
  });
3039
3610
  showInfoToast({
@@ -3059,12 +3630,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3059
3630
  }
3060
3631
  };
3061
3632
  if (typeof document !== "undefined") {
3633
+ document.addEventListener("mousedown", handleMouseDown, true);
3062
3634
  document.addEventListener("click", handleCtrlClick, true);
3063
3635
  return () => {
3636
+ document.removeEventListener("mousedown", handleMouseDown, true);
3064
3637
  document.removeEventListener("click", handleCtrlClick, true);
3065
3638
  };
3066
3639
  }
3067
- }, [loadItem, switchView, showInfoToast, showErrorToast]);
3640
+ }, [loadItem, switchWorkspace, showInfoToast, showErrorToast]);
3068
3641
  useEffect(() => {
3069
3642
  const handleGlobalBlur = () => {
3070
3643
  operations.onFieldBlur?.();
@@ -3102,21 +3675,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3102
3675
  // Otherwise, only show workspaces that are in the settings
3103
3676
  return workspaceIdsFromSettings.includes(w.id) && !w.visible;
3104
3677
  });
3105
- // Get sidebars allowed for the current workspace from settings
3106
- const currentWorkspaceSettings = userInfo.workspaces?.find((w) => w.id === workspaceId);
3107
- const allowedSidebarIds = currentWorkspaceSettings?.sidebars ?? [];
3108
- // Legacy: Calculate visible views for backwards compatibility
3109
- const allViews = (configuration.editor.views ?? [])
3110
- .filter((x) => {
3111
- return !x.visible && !x.hidden;
3112
- })
3113
- .filter((x) => !userInfo.views ||
3114
- userInfo.views.map((view) => view.name).includes(x.name));
3115
- const pinnedViews = userInfo.preferences?.pinnedViews ||
3116
- configuration.editor.defaultPinnedViews ||
3117
- [];
3118
- // Legacy visibleViews for backwards compatibility
3119
- const visibleViews = allViews.filter((view) => view.name === viewName || pinnedViews.includes(view.name));
3120
3678
  // Handle initial mode setup from URL (only on initial load)
3121
3679
  useEffect(() => {
3122
3680
  if (!isInitialLoad)
@@ -3161,7 +3719,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3161
3719
  // This is especially important when called from the tour, where the current workspace
3162
3720
  // might be the editor and URL-only navigation would get "corrected" back.
3163
3721
  try {
3164
- switchView("home", {
3722
+ switchWorkspace("home", {
3165
3723
  skipConfirmation: true,
3166
3724
  skipNavigationHistory: true,
3167
3725
  });
@@ -3208,7 +3766,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3208
3766
  const current = new URLSearchParams(searchParams.toString());
3209
3767
  current.delete("version");
3210
3768
  current.delete("itemid");
3211
- current.delete("view"); // Remove legacy param
3212
3769
  current.delete("workspace"); // Clear workspace
3213
3770
  current.set("create", "1");
3214
3771
  const newUrl = `${pathname}?${current.toString()}`;
@@ -3488,6 +4045,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3488
4045
  setShowOnlyMyChanges,
3489
4046
  filterByCurrentLanguage,
3490
4047
  setFilterByCurrentLanguage,
4048
+ historySearchQuery,
4049
+ setHistorySearchQuery,
3491
4050
  refreshHistory,
3492
4051
  isRefreshing,
3493
4052
  activeSessions,
@@ -3525,19 +4084,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3525
4084
  workspaceId,
3526
4085
  previousWorkspaceId,
3527
4086
  switchWorkspace,
3528
- // Sidebar state - filter by workspace settings if available
3529
- availableSidebars: (configuration.editor.sidebars ?? []).filter((s) => {
3530
- // Always show agents-panel regardless of workspace settings
3531
- if (s.id === "agents-panel") {
3532
- return true;
3533
- }
3534
- // If no workspace settings or no sidebars defined for current workspace, show all
3535
- if (!allowedSidebarIds || allowedSidebarIds.length === 0) {
3536
- return true;
3537
- }
3538
- // Only show sidebars that are in the allowed list for the current workspace
3539
- return allowedSidebarIds.includes(s.id);
3540
- }),
4087
+ // Sidebar state
4088
+ availableSidebars: getSidebarsForWorkspace(workspaceId),
3541
4089
  openSidebars,
3542
4090
  pinnedSidebars,
3543
4091
  lockedSidebars,
@@ -3547,17 +4095,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3547
4095
  toggleSidebarPin,
3548
4096
  toggleSidebarLock,
3549
4097
  stackSidebar,
4098
+ moveSidebarToColumn,
3550
4099
  unstackSidebar,
3551
4100
  reorderSidebarInStack,
3552
4101
  reorderPinnedSidebars,
3553
4102
  reorderOpenSidebars,
3554
4103
  getResolvedSidebar,
3555
- // Legacy compatibility (deprecated)
3556
- viewName,
3557
- previousViewName,
3558
- switchView,
3559
- view: currentView,
3560
- visibleViews,
3561
4104
  compareMode,
3562
4105
  setCompareMode,
3563
4106
  fullscreen,
@@ -3597,6 +4140,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3597
4140
  addSocketMessageListener,
3598
4141
  sendSocketMessage,
3599
4142
  socketConnectionVersion,
4143
+ socketDiagnostics,
3600
4144
  currentItemDescriptor,
3601
4145
  editorSlots,
3602
4146
  activeSlotId,
@@ -3609,6 +4153,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3609
4153
  setActiveSlot,
3610
4154
  revision,
3611
4155
  notifyPageModelReady,
4156
+ pageModelReadyToken,
3612
4157
  selectedComment,
3613
4158
  setSelectedComment,
3614
4159
  comments,
@@ -3687,6 +4232,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3687
4232
  setEnableCompletions,
3688
4233
  showComponentNavigator,
3689
4234
  setShowComponentNavigator: handleSetShowComponentNavigator,
4235
+ isComponentNavigatorOpenForSlot,
4236
+ setComponentNavigatorOpenForSlot,
3690
4237
  showAgentsPanel,
3691
4238
  setShowAgentsPanel: handleSetShowAgentsPanel,
3692
4239
  showMinimap,
@@ -3698,8 +4245,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3698
4245
  helpTerminalProfileName,
3699
4246
  helpTerminalActiveTab,
3700
4247
  setHelpTerminalActiveTab,
4248
+ selectedHelpSectionId,
4249
+ setSelectedHelpSectionId,
3701
4250
  showAgentsWorkspaceEditor,
3702
4251
  setShowAgentsWorkspaceEditor: handleSetShowAgentsWorkspaceEditor,
4252
+ selectedAgentsWorkspaceAgentId,
4253
+ setSelectedAgentsWorkspaceAgentId,
3703
4254
  activeEditorTab,
3704
4255
  setActiveEditorTab,
3705
4256
  showLayoutComponents,
@@ -3708,6 +4259,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3708
4259
  isQuotaExceeded: isQuotaExceeded(),
3709
4260
  getQuotaWarningMessage,
3710
4261
  isMobile,
4262
+ mobileEditorPanelOpen,
4263
+ setMobileEditorPanelOpen: handleSetMobileEditorPanelOpen,
3711
4264
  openDialog,
3712
4265
  webSocketMessages,
3713
4266
  clearWebSocketMessages: () => setWebSocketMessages([]),
@@ -3746,7 +4299,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3746
4299
  configuration,
3747
4300
  updateUrl,
3748
4301
  workspaceId,
3749
- switchView,
3750
4302
  pathname,
3751
4303
  router,
3752
4304
  item,
@@ -3776,7 +4328,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3776
4328
  currentWorkspace,
3777
4329
  previousWorkspaceId,
3778
4330
  switchWorkspace,
3779
- allowedSidebarIds,
3780
4331
  openSidebars,
3781
4332
  pinnedSidebars,
3782
4333
  lockedSidebars,
@@ -3788,9 +4339,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3788
4339
  reorderOpenSidebars,
3789
4340
  getResolvedSidebar,
3790
4341
  viewName,
3791
- previousViewName,
3792
- currentView,
3793
- visibleViews,
3794
4342
  compareMode,
3795
4343
  // Important: in multi-slot mode the active PageViewContext can change
3796
4344
  // without the base `pageViewContext` identity changing (e.g. switching slots).
@@ -3815,6 +4363,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3815
4363
  currentItemDescriptor,
3816
4364
  revision,
3817
4365
  notifyPageModelReady,
4366
+ pageModelReadyToken,
3818
4367
  selectedComment,
3819
4368
  comments,
3820
4369
  availableCommentTags,
@@ -3833,6 +4382,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3833
4382
  quickSwitcherSelectedIndex,
3834
4383
  handleQuickSwitcherSelect,
3835
4384
  webSocketMessages,
4385
+ socketDiagnostics,
3836
4386
  factoriesRef,
3837
4387
  user,
3838
4388
  statusMessage,
@@ -3841,7 +4391,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3841
4391
  isQuotaExceeded,
3842
4392
  getQuotaWarningMessage,
3843
4393
  isMobile,
4394
+ mobileEditorPanelOpen,
4395
+ handleSetMobileEditorPanelOpen,
3844
4396
  showComponentNavigator,
4397
+ isComponentNavigatorOpenForSlot,
4398
+ setComponentNavigatorOpenForSlot,
3845
4399
  handleSetShowComponentNavigator,
3846
4400
  showAgentsPanel,
3847
4401
  handleSetShowAgentsPanel,
@@ -3853,7 +4407,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3853
4407
  helpTerminalProfileName,
3854
4408
  helpTerminalActiveTab,
3855
4409
  setHelpTerminalActiveTab,
4410
+ selectedHelpSectionId,
3856
4411
  showAgentsWorkspaceEditor,
4412
+ selectedAgentsWorkspaceAgentId,
3857
4413
  activeEditorTab,
3858
4414
  showLayoutComponents,
3859
4415
  openDialog,
@@ -4098,18 +4654,40 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4098
4654
  // prevDependencies.current = currentDependencies;
4099
4655
  // editContextRef.current = editContext;
4100
4656
  // }, [editContext]);
4657
+ // Auto-open the mobile editor panel for new selection/intent changes, but
4658
+ // keep a manual close sticky for the exact same context.
4659
+ useEffect(() => {
4660
+ if (!isMobile || workspaceId !== "editor")
4661
+ return;
4662
+ if (activeEditorTab) {
4663
+ handleSetMobileEditorPanelOpen(true);
4664
+ return;
4665
+ }
4666
+ if (!(selection.length > 0 || insertMode))
4667
+ return;
4668
+ if (dismissedMobilePanelToken === mobilePanelDismissToken)
4669
+ return;
4670
+ handleSetMobileEditorPanelOpen(true);
4671
+ }, [
4672
+ activeEditorTab,
4673
+ dismissedMobilePanelToken,
4674
+ handleSetMobileEditorPanelOpen,
4675
+ insertMode,
4676
+ isMobile,
4677
+ mobilePanelDismissToken,
4678
+ selection,
4679
+ workspaceId,
4680
+ ]);
4101
4681
  useEffect(() => {
4102
4682
  fieldsEditContext.clearModifiedFields();
4103
4683
  }, [currentItemDescriptor]);
4104
- if (!currentView)
4684
+ if (!currentWorkspace)
4105
4685
  return null;
4106
- const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fixed inset-0 flex", children: [_jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }), _jsx(FullscreenControls, { device: activePageViewContext.device, setDevice: (d) => activePageViewContext.setDevice(d), canExit: !configuration.forceFullscreen, onExit: () => setFullscreen(false), firstMobileDeviceName: configuration.devices[0]?.name })] }), showFullscreenHint && !configuration.forceFullscreen && (_jsx("div", { className: "fixed inset-0", onMouseMoveCapture: () => {
4686
+ const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fixed inset-0 flex", children: [_jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }), _jsx(FullscreenControls, { device: activePageViewContext.device, setDevice: (d) => activePageViewContext.setDevice(d), canExit: !configuration.forceFullscreen, onExit: () => setFullscreen(false), firstMobileDeviceName: configuration.devices[0]?.name })] }), showFullscreenHint && !configuration.forceFullscreen && !isMobile && (_jsx("div", { className: "fixed inset-0 z-10000", onMouseMoveCapture: () => {
4107
4687
  setTimeout(() => {
4108
4688
  setShowFullscreenHint(false);
4109
4689
  }, 600);
4110
- }, "data-testid": "fullscreen-hint-overlay", children: _jsxs("div", { className: "fixed top-6 left-1/2 -translate-x-1/2 transform rounded-full bg-black/60 px-6 py-2.5 text-sm font-medium text-white shadow-2xl backdrop-blur-md transition-all duration-500", children: ["Press", " ", _jsx("kbd", { className: "mx-1 rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold", children: "Ctrl + F11" }), " ", "to exit fullscreen"] }) }))] })) : (_jsxs(_Fragment, { children: [_jsx(EditorChrome, { className: className, currentWorkspace: currentWorkspace, centerPanelView: centerPanelView, editContext: editContext, showComponentNavigator: showComponentNavigator, handleSetShowComponentNavigator: handleSetShowComponentNavigator, showAgentsPanel: showAgentsPanel, handleSetShowAgentsPanel: handleSetShowAgentsPanel, workspaceId: workspaceId,
4111
- // Legacy props for backwards compatibility
4112
- currentView: currentView, viewName: viewName }), isTourActive && (_jsx(Tour, { tourStopCallback: () => {
4690
+ }, "data-testid": "fullscreen-hint-overlay", children: _jsxs("div", { className: "fixed top-6 left-1/2 -translate-x-1/2 transform rounded-full bg-black/60 px-6 py-2.5 text-sm font-medium text-white shadow-2xl backdrop-blur-md transition-all duration-500", children: ["Press", " ", _jsx("kbd", { className: "mx-1 rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold", children: "Ctrl + F11" }), " ", "to exit fullscreen"] }) }))] })) : (_jsxs(_Fragment, { children: [_jsx(EditorChrome, { className: className, currentWorkspace: currentWorkspace, centerPanelView: centerPanelView, editContext: editContext, showAgentsPanel: showAgentsPanel, handleSetShowAgentsPanel: handleSetShowAgentsPanel, workspaceId: workspaceId }), isTourActive && (_jsx(Tour, { tourStopCallback: () => {
4113
4691
  setIsTourActive(false);
4114
4692
  // Remove tour state from URL
4115
4693
  // Use history.replaceState instead of router.replace to avoid triggering React navigation
@@ -4119,8 +4697,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4119
4697
  const newUrl = queryString
4120
4698
  ? `${window.location.pathname}?${queryString}`
4121
4699
  : window.location.pathname;
4122
- window.history.replaceState(null, "", newUrl);
4123
- }, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(GuidanceOverlay, {}), _jsx(AgentDialogHandler, { sendWebSocketMessage: (type, payload) => sendSocketMessage({ type, payload }) })] }));
4124
- return (_jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsxs(EditContextProvider, { value: editContext, children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-white/70 backdrop-blur-[1px]", children: _jsx("div", { className: "flex items-center gap-3 rounded-md border border-gray-200 bg-white px-4 py-3 text-gray-700 shadow-sm", children: _jsx(Spinner, { size: "xl" }) }) })), editContext.isRefreshing && (_jsx("div", { className: "pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center text-gray-600 opacity-50 select-none", children: _jsx(Spinner, {}) })), children || editorUi, dialog, _jsx(Toaster, { position: "top-center" }), " ", _jsx(ConfirmationDialog, { ref: confirmationDialogRef }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: navigationHistory.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef }), _jsx(InlineAiTrigger, {}), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef })] }) }) }) }));
4700
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
4701
+ }, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(GuidanceOverlay, {}) }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(AgentDialogHandler, {}) })] }));
4702
+ return (_jsx(LicenseProvider, { initialLicenseStatus: initialLicenseStatus, initialStatusLoaded: initialLicenseStatusLoaded, children: _jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsxs(EditContextProvider, { value: editContext, children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-white/70 backdrop-blur-[1px]", children: _jsx("div", { className: "flex items-center gap-3 rounded-md border border-gray-200 bg-white px-4 py-3 text-gray-700 shadow-sm", children: _jsx(Spinner, { size: "xl" }) }) })), editContext.isRefreshing && (_jsx("div", { className: "pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center text-gray-600 opacity-50 select-none", children: _jsx(Spinner, {}) })), (currentWorkspace.id === "agents" ||
4703
+ currentWorkspace.id === "taskboard") &&
4704
+ showAgentsWorkspaceEditor && (_jsx(AgentsSlotContextBridgeHost, { slots: editorSlots })), startupChecks.state !== "loading" && (children || editorUi), startupChecks.state !== "loading" && dialog, _jsx(Toaster, { position: "top-center" }), " ", _jsx(ConfirmationDialog, { ref: confirmationDialogRef }), _jsx(ConcurrentUserLimitDialog, { open: concurrentUserLimitError !== null, onOpenChange: (open) => {
4705
+ if (!open) {
4706
+ setConcurrentUserLimitError(null);
4707
+ }
4708
+ }, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {})] }) }) }) }) }));
4125
4709
  }
4126
4710
  //# sourceMappingURL=EditorShell.js.map