@parhelia/core 0.1.12602 → 0.1.12614

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 (605) 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 +37 -63
  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 +6 -5
  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 +2515 -579
  117. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  118. package/dist/editor/ai/AgentTerminalStatusBar.d.ts +9 -4
  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/EditOperationsPanel.d.ts +3 -2
  130. package/dist/editor/ai/EditOperationsPanel.js +21 -78
  131. package/dist/editor/ai/EditOperationsPanel.js.map +1 -1
  132. package/dist/editor/ai/GuidanceOverlay.js +17 -11
  133. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  134. package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
  135. package/dist/editor/ai/InlineAiDialog.js +514 -192
  136. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  137. package/dist/editor/ai/InlineAiTrigger.js +115 -12
  138. package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
  139. package/dist/editor/ai/MediaImage.js +40 -8
  140. package/dist/editor/ai/MediaImage.js.map +1 -1
  141. package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
  142. package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
  143. package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
  144. package/dist/editor/ai/ToolCallDisplay.js +614 -202
  145. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  146. package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
  147. package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
  148. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  149. package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
  150. package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
  151. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  152. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +117 -0
  153. package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
  154. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  155. package/dist/editor/ai/types.d.ts +3 -1
  156. package/dist/editor/ai/useAgentStatus.d.ts +2 -1
  157. package/dist/editor/ai/useAgentStatus.js +90 -100
  158. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  159. package/dist/editor/ai/useInlineAiPosition.js +45 -5
  160. package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
  161. package/dist/editor/client/AboutDialog.js +4 -2
  162. package/dist/editor/client/AboutDialog.js.map +1 -1
  163. package/dist/editor/client/EditorShell.d.ts +4 -1
  164. package/dist/editor/client/EditorShell.js +966 -280
  165. package/dist/editor/client/EditorShell.js.map +1 -1
  166. package/dist/editor/client/editContext.d.ts +45 -19
  167. package/dist/editor/client/editContext.js.map +1 -1
  168. package/dist/editor/client/helpers.js +12 -11
  169. package/dist/editor/client/helpers.js.map +1 -1
  170. package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
  171. package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
  172. package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
  173. package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
  174. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  175. package/dist/editor/client/hooks/useQuota.d.ts +8 -0
  176. package/dist/editor/client/hooks/useQuota.js.map +1 -1
  177. package/dist/editor/client/hooks/useSocketMessageHandler.js +73 -15
  178. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  179. package/dist/editor/client/itemsRepository.js +10 -6
  180. package/dist/editor/client/itemsRepository.js.map +1 -1
  181. package/dist/editor/client/operations.d.ts +6 -3
  182. package/dist/editor/client/operations.js +208 -30
  183. package/dist/editor/client/operations.js.map +1 -1
  184. package/dist/editor/client/pageModelBuilder.js +4 -31
  185. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  186. package/dist/editor/client/ui/DevModeIndicator.js +2 -2
  187. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
  188. package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
  189. package/dist/editor/client/ui/EditorChrome.js +55 -72
  190. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  191. package/dist/editor/client/ui/FullscreenControls.js +5 -3
  192. package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
  193. package/dist/editor/commands/commands.d.ts +11 -1
  194. package/dist/editor/commands/commands.js +12 -1
  195. package/dist/editor/commands/commands.js.map +1 -1
  196. package/dist/editor/commands/componentCommands.js +109 -55
  197. package/dist/editor/commands/componentCommands.js.map +1 -1
  198. package/dist/editor/commands/customCommandConverter.d.ts +8 -1
  199. package/dist/editor/commands/customCommandConverter.js +35 -5
  200. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  201. package/dist/editor/commands/handlers/agentHandler.js +2 -1
  202. package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
  203. package/dist/editor/commands/itemCommands.d.ts +3 -0
  204. package/dist/editor/commands/itemCommands.js +93 -10
  205. package/dist/editor/commands/itemCommands.js.map +1 -1
  206. package/dist/editor/commands/undo.d.ts +9 -15
  207. package/dist/editor/commands/undo.js +24 -0
  208. package/dist/editor/commands/undo.js.map +1 -1
  209. package/dist/editor/context-menu/InsertMenu.js +83 -39
  210. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  211. package/dist/editor/field-types/MultiLineText.js +1 -1
  212. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  213. package/dist/editor/field-types/RawEditor.js +1 -1
  214. package/dist/editor/field-types/RichTextEditor.js +13 -5
  215. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  216. package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
  217. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  218. package/dist/editor/field-types/SingleLineText.js +1 -1
  219. package/dist/editor/field-types/TreeListEditor.js +3 -2
  220. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  221. package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
  222. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
  223. package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
  224. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  225. package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
  226. package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
  227. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
  228. package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
  229. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  230. package/dist/editor/field-types/richtext/types.d.ts +2 -0
  231. package/dist/editor/field-types/richtext/types.js.map +1 -1
  232. package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
  233. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
  234. package/dist/editor/field-types/textContextMenuFactory.js +3 -2
  235. package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
  236. package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
  237. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  238. package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
  239. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  240. package/dist/editor/media-selector/MediaSelector.js +7 -1
  241. package/dist/editor/media-selector/MediaSelector.js.map +1 -1
  242. package/dist/editor/media-selector/TreeSelector.js +40 -35
  243. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  244. package/dist/editor/menubar/ActiveUsers.js +1 -1
  245. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  246. package/dist/editor/menubar/GenericToolbar.js +4 -2
  247. package/dist/editor/menubar/GenericToolbar.js.map +1 -1
  248. package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
  249. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  250. package/dist/editor/menubar/PageSelector.js +26 -147
  251. package/dist/editor/menubar/PageSelector.js.map +1 -1
  252. package/dist/editor/menubar/Separator.js +1 -1
  253. package/dist/editor/menubar/VersionSelector.js +2 -4
  254. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  255. package/dist/editor/menubar/WorkflowButton.js +39 -12
  256. package/dist/editor/menubar/WorkflowButton.js.map +1 -1
  257. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
  258. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
  259. package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
  260. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  261. package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
  262. package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
  263. package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
  264. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
  265. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  266. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
  267. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  268. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +12 -2
  269. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  270. package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
  271. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  272. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  273. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  274. package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
  275. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  276. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
  277. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  278. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
  279. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  280. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
  281. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  282. package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
  283. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  284. package/dist/editor/page-viewer/EditorForm.js +69 -11
  285. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  286. package/dist/editor/page-viewer/EditorFormHintPopover.d.ts +7 -0
  287. package/dist/editor/page-viewer/EditorFormHintPopover.js +55 -0
  288. package/dist/editor/page-viewer/EditorFormHintPopover.js.map +1 -0
  289. package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
  290. package/dist/editor/page-viewer/MiniMap.js +183 -33
  291. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  292. package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
  293. package/dist/editor/page-viewer/PageViewer.js +67 -52
  294. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  295. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  296. package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
  297. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  298. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
  299. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  300. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  301. package/dist/editor/page-viewer/pageViewContext.js +51 -14
  302. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  303. package/dist/editor/pageModel.d.ts +14 -1
  304. package/dist/editor/reviews/Comment.d.ts +2 -1
  305. package/dist/editor/reviews/Comment.js +92 -15
  306. package/dist/editor/reviews/Comment.js.map +1 -1
  307. package/dist/editor/reviews/CommentDisplayPopover.js +70 -5
  308. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  309. package/dist/editor/reviews/CommentView.d.ts +3 -1
  310. package/dist/editor/reviews/CommentView.js +26 -6
  311. package/dist/editor/reviews/CommentView.js.map +1 -1
  312. package/dist/editor/reviews/Comments.js +140 -75
  313. package/dist/editor/reviews/Comments.js.map +1 -1
  314. package/dist/editor/reviews/CreateReviewDialog.js +281 -177
  315. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  316. package/dist/editor/reviews/DecisionsMatrix.js +96 -25
  317. package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
  318. package/dist/editor/reviews/DiffView.js +7 -14
  319. package/dist/editor/reviews/DiffView.js.map +1 -1
  320. package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
  321. package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
  322. package/dist/editor/reviews/MultiReviewManager.js +25 -3
  323. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  324. package/dist/editor/reviews/PagesPanel.js +31 -15
  325. package/dist/editor/reviews/PagesPanel.js.map +1 -1
  326. package/dist/editor/reviews/PreviewInfo.js +1 -4
  327. package/dist/editor/reviews/PreviewInfo.js.map +1 -1
  328. package/dist/editor/reviews/ReviewCard.js +13 -7
  329. package/dist/editor/reviews/ReviewCard.js.map +1 -1
  330. package/dist/editor/reviews/ReviewDetail.js +3 -2
  331. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  332. package/dist/editor/reviews/ReviewsList.js +7 -3
  333. package/dist/editor/reviews/ReviewsList.js.map +1 -1
  334. package/dist/editor/reviews/SuggestedEdit.js +34 -3
  335. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  336. package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
  337. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  338. package/dist/editor/reviews/commentAi.js +25 -6
  339. package/dist/editor/reviews/commentAi.js.map +1 -1
  340. package/dist/editor/reviews/reviewCommands.js +4 -1
  341. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  342. package/dist/editor/reviews/useMultiReview.js +2 -2
  343. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  344. package/dist/editor/reviews/useReviews.d.ts +4 -3
  345. package/dist/editor/reviews/useReviews.js +21 -32
  346. package/dist/editor/reviews/useReviews.js.map +1 -1
  347. package/dist/editor/services/agentService.d.ts +240 -5
  348. package/dist/editor/services/agentService.js +299 -39
  349. package/dist/editor/services/agentService.js.map +1 -1
  350. package/dist/editor/services/aiService.d.ts +57 -1
  351. package/dist/editor/services/aiService.js +79 -6
  352. package/dist/editor/services/aiService.js.map +1 -1
  353. package/dist/editor/services/contentService.d.ts +6 -3
  354. package/dist/editor/services/contentService.js +13 -12
  355. package/dist/editor/services/contentService.js.map +1 -1
  356. package/dist/editor/services/editService.d.ts +52 -1
  357. package/dist/editor/services/editService.js +121 -13
  358. package/dist/editor/services/editService.js.map +1 -1
  359. package/dist/editor/services/indexService.js +1 -1
  360. package/dist/editor/services/indexService.js.map +1 -1
  361. package/dist/editor/services/reviewsService.d.ts +3 -6
  362. package/dist/editor/services/reviewsService.js +2 -11
  363. package/dist/editor/services/reviewsService.js.map +1 -1
  364. package/dist/editor/services/serviceHelper.d.ts +2 -1
  365. package/dist/editor/services/serviceHelper.js +174 -22
  366. package/dist/editor/services/serviceHelper.js.map +1 -1
  367. package/dist/editor/services/systemService.d.ts +2 -1
  368. package/dist/editor/services/systemService.js +3 -0
  369. package/dist/editor/services/systemService.js.map +1 -1
  370. package/dist/editor/services-server/api.d.ts +1 -2
  371. package/dist/editor/services-server/api.js +11 -6
  372. package/dist/editor/services-server/api.js.map +1 -1
  373. package/dist/editor/settings/About.js +344 -3
  374. package/dist/editor/settings/About.js.map +1 -1
  375. package/dist/editor/settings/IndexOverview.js +3 -1
  376. package/dist/editor/settings/IndexOverview.js.map +1 -1
  377. package/dist/editor/settings/QuotaInfo.js +210 -4
  378. package/dist/editor/settings/QuotaInfo.js.map +1 -1
  379. package/dist/editor/settings/SettingsView.js +25 -23
  380. package/dist/editor/settings/SettingsView.js.map +1 -1
  381. package/dist/editor/settings/Status.js +7 -6
  382. package/dist/editor/settings/Status.js.map +1 -1
  383. package/dist/editor/settings/index/useIndexStatus.js +23 -22
  384. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  385. package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
  386. package/dist/editor/settings/panels/AgentsPanel.js +95 -121
  387. package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
  388. package/dist/editor/settings/panels/ModelsPanel.js +324 -108
  389. package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
  390. package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
  391. package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
  392. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  393. package/dist/editor/settings/panels/SearchConfigPanel.js +67 -6
  394. package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
  395. package/dist/editor/settings/panels/StatusPanel.js +7 -2
  396. package/dist/editor/settings/panels/StatusPanel.js.map +1 -1
  397. package/dist/editor/settings/panels/index.d.ts +3 -2
  398. package/dist/editor/settings/panels/index.js +3 -2
  399. package/dist/editor/settings/panels/index.js.map +1 -1
  400. package/dist/editor/settings/status/coreStatusChecks.js +124 -19
  401. package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
  402. package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
  403. package/dist/editor/settings/status/useStartupChecks.js +9 -5
  404. package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
  405. package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
  406. package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
  407. package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
  408. package/dist/editor/setup-wizard/steps/LicenseActivationStep.js +39 -2
  409. package/dist/editor/setup-wizard/steps/LicenseActivationStep.js.map +1 -1
  410. package/dist/editor/sidebar/ComponentPalette.js +2 -1
  411. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  412. package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
  413. package/dist/editor/sidebar/ComponentTree.js +216 -69
  414. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  415. package/dist/editor/sidebar/EditHistory.js +22 -46
  416. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  417. package/dist/editor/sidebar/Favorites.js +4 -8
  418. package/dist/editor/sidebar/Favorites.js.map +1 -1
  419. package/dist/editor/sidebar/MainContentTree.js +4 -3
  420. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  421. package/dist/editor/sidebar/OperationItem.js +21 -7
  422. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  423. package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
  424. package/dist/editor/sidebar/SidebarPanel.js +44 -12
  425. package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
  426. package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
  427. package/dist/editor/sidebar/SidebarStack.js +4 -3
  428. package/dist/editor/sidebar/SidebarStack.js.map +1 -1
  429. package/dist/editor/sidebar/Validation.js +24 -12
  430. package/dist/editor/sidebar/Validation.js.map +1 -1
  431. package/dist/editor/sidebar/Workbox.js +53 -3
  432. package/dist/editor/sidebar/Workbox.js.map +1 -1
  433. package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
  434. package/dist/editor/sidebar/WorkspaceRail.js +56 -167
  435. package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
  436. package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
  437. package/dist/editor/tree-indicators/GutterColumns.js +26 -5
  438. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  439. package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
  440. package/dist/editor/tree-indicators/GutterContext.js +23 -0
  441. package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
  442. package/dist/editor/tree-indicators/index.d.ts +0 -1
  443. package/dist/editor/tree-indicators/index.js +0 -1
  444. package/dist/editor/tree-indicators/index.js.map +1 -1
  445. package/dist/editor/tree-indicators/types.d.ts +12 -1
  446. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
  447. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
  448. package/dist/editor/ui/Icons.js +1 -1
  449. package/dist/editor/ui/Icons.js.map +1 -1
  450. package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
  451. package/dist/editor/ui/ItemNameDialogNew.js +33 -17
  452. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  453. package/dist/editor/ui/ItemSearch.js +7 -11
  454. package/dist/editor/ui/ItemSearch.js.map +1 -1
  455. package/dist/editor/ui/SimpleIconButton.js +1 -1
  456. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  457. package/dist/editor/ui/SimpleTabs.d.ts +1 -0
  458. package/dist/editor/ui/SimpleTabs.js +45 -25
  459. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  460. package/dist/editor/ui/Splitter.d.ts +1 -0
  461. package/dist/editor/ui/Splitter.js +102 -86
  462. package/dist/editor/ui/Splitter.js.map +1 -1
  463. package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
  464. package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
  465. package/dist/editor/ui/TreeListSelector.d.ts +6 -1
  466. package/dist/editor/ui/TreeListSelector.js +2 -2
  467. package/dist/editor/ui/TreeListSelector.js.map +1 -1
  468. package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
  469. package/dist/editor/utils/keyboardNavigation.js +48 -140
  470. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  471. package/dist/editor/utils.js +19 -9
  472. package/dist/editor/utils.js.map +1 -1
  473. package/dist/editor/views/CompareView.d.ts +3 -1
  474. package/dist/editor/views/CompareView.js +7 -5
  475. package/dist/editor/views/CompareView.js.map +1 -1
  476. package/dist/editor/views/EditView.js +1 -1
  477. package/dist/editor/views/EditView.js.map +1 -1
  478. package/dist/editor/views/EditorSlot.js +27 -34
  479. package/dist/editor/views/EditorSlot.js.map +1 -1
  480. package/dist/editor/views/ItemEditor.js +7 -3
  481. package/dist/editor/views/ItemEditor.js.map +1 -1
  482. package/dist/editor/views/MediaFolderEditView.js +1 -1
  483. package/dist/editor/views/MediaFolderEditView.js.map +1 -1
  484. package/dist/editor/views/ParheliaView.js +5 -6
  485. package/dist/editor/views/ParheliaView.js.map +1 -1
  486. package/dist/editor/views/SingleEditView.d.ts +2 -1
  487. package/dist/editor/views/SingleEditView.js +10 -8
  488. package/dist/editor/views/SingleEditView.js.map +1 -1
  489. package/dist/editor/views/editorSlotContext.js +35 -6
  490. package/dist/editor/views/editorSlotContext.js.map +1 -1
  491. package/dist/index.d.ts +16 -2
  492. package/dist/index.js +11 -0
  493. package/dist/index.js.map +1 -1
  494. package/dist/licensing/LicenseCodeEntry.d.ts +2 -1
  495. package/dist/licensing/LicenseCodeEntry.js +31 -2
  496. package/dist/licensing/LicenseCodeEntry.js.map +1 -1
  497. package/dist/licensing/LicenseContext.d.ts +2 -0
  498. package/dist/licensing/LicenseContext.js +29 -0
  499. package/dist/licensing/LicenseContext.js.map +1 -1
  500. package/dist/licensing/LicenseOverlay.js +2 -2
  501. package/dist/licensing/LicenseOverlay.js.map +1 -1
  502. package/dist/licensing/licenseService.js +8 -0
  503. package/dist/licensing/licenseService.js.map +1 -1
  504. package/dist/licensing/types.d.ts +2 -1
  505. package/dist/licensing/types.js.map +1 -1
  506. package/dist/revision.d.ts +2 -2
  507. package/dist/revision.js +2 -2
  508. package/dist/setup/services/setupWizardService.d.ts +48 -14
  509. package/dist/setup/services/setupWizardService.js +52 -17
  510. package/dist/setup/services/setupWizardService.js.map +1 -1
  511. package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
  512. package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
  513. package/dist/setup/wizard/steps/ImportModelDialog.js +46 -22
  514. package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
  515. package/dist/splash-screen/ModernSplashScreen.js +112 -32
  516. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  517. package/dist/splash-screen/NewPage.js +33 -50
  518. package/dist/splash-screen/NewPage.js.map +1 -1
  519. package/dist/splash-screen/OpenPage.js +2 -6
  520. package/dist/splash-screen/OpenPage.js.map +1 -1
  521. package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
  522. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  523. package/dist/splash-screen/ParheliaLogo.js +87 -37
  524. package/dist/splash-screen/ParheliaLogo.js.map +1 -1
  525. package/dist/splash-screen/RecentPages.js +3 -3
  526. package/dist/splash-screen/RecentPages.js.map +1 -1
  527. package/dist/task-board/services/taskService.js +10 -3
  528. package/dist/task-board/services/taskService.js.map +1 -1
  529. package/dist/tour/Tour.d.ts +2 -1
  530. package/dist/tour/Tour.js +256 -75
  531. package/dist/tour/Tour.js.map +1 -1
  532. package/dist/tour/default-tour.js +222 -96
  533. package/dist/tour/default-tour.js.map +1 -1
  534. package/dist/types.d.ts +74 -29
  535. package/package.json +19 -15
  536. package/styles.css +39 -10
  537. package/dist/editor/ComponentInfo.d.ts +0 -4
  538. package/dist/editor/ComponentInfo.js +0 -41
  539. package/dist/editor/ComponentInfo.js.map +0 -1
  540. package/dist/editor/ai/HelpTerminal.d.ts +0 -5
  541. package/dist/editor/ai/HelpTerminal.js +0 -166
  542. package/dist/editor/ai/HelpTerminal.js.map +0 -1
  543. package/dist/editor/field-types/ReactQuill.d.ts +0 -125
  544. package/dist/editor/field-types/ReactQuill.js +0 -385
  545. package/dist/editor/field-types/ReactQuill.js.map +0 -1
  546. package/dist/editor/services-server/graphQL.d.ts +0 -29
  547. package/dist/editor/services-server/graphQL.js +0 -53
  548. package/dist/editor/services-server/graphQL.js.map +0 -1
  549. package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
  550. package/dist/editor/settings/AllAgentsPanel.js +0 -139
  551. package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
  552. package/dist/editor/settings/LatestFeedback.d.ts +0 -1
  553. package/dist/editor/settings/LatestFeedback.js +0 -136
  554. package/dist/editor/settings/LatestFeedback.js.map +0 -1
  555. package/dist/editor/settings/Setup.d.ts +0 -1
  556. package/dist/editor/settings/Setup.js +0 -211
  557. package/dist/editor/settings/Setup.js.map +0 -1
  558. package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
  559. package/dist/editor/settings/panels/DatabasePanel.js +0 -50
  560. package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
  561. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
  562. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
  563. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
  564. package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
  565. package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
  566. package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
  567. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
  568. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
  569. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
  570. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
  571. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
  572. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
  573. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
  574. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
  575. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
  576. package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
  577. package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
  578. package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
  579. package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
  580. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
  581. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
  582. package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
  583. package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
  584. package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
  585. package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
  586. package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
  587. package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
  588. package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
  589. package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
  590. package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
  591. package/dist/editor/sidebar/Debug.d.ts +0 -1
  592. package/dist/editor/sidebar/Debug.js +0 -70
  593. package/dist/editor/sidebar/Debug.js.map +0 -1
  594. package/dist/editor/sidebar/GraphQL.d.ts +0 -2
  595. package/dist/editor/sidebar/GraphQL.js +0 -234
  596. package/dist/editor/sidebar/GraphQL.js.map +0 -1
  597. package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
  598. package/dist/editor/sidebar/LeftToolbar.js +0 -12
  599. package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
  600. package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
  601. package/dist/editor/sidebar/NavigationSidebar.js +0 -254
  602. package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
  603. package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
  604. package/dist/editor/tree-indicators/GutterSelector.js +0 -91
  605. 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";
@@ -19,7 +21,9 @@ import ConfirmationDialog from "../ConfirmationDialog";
19
21
  import { getItemDescriptor } from "../utils";
20
22
  import { EditContextMenu } from "../ContextMenu";
21
23
  import { InlineAiTrigger } from "../ai/InlineAiTrigger";
24
+ import { EditorFormHintPopover } from "../page-viewer/EditorFormHintPopover";
22
25
  import { FieldEditorPopup } from "../FieldEditorPopup";
26
+ import { ConcurrentUserLimitDialog } from "../ConcurrentUserLimitDialog";
23
27
  import { post } from "../services/serviceHelper";
24
28
  import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
25
29
  import { useItemsRepository } from "./itemsRepository";
@@ -33,6 +37,7 @@ import { GuidanceOverlay } from "../ai/GuidanceOverlay";
33
37
  import { AgentDialogHandler } from "../ai/dialogs";
34
38
  import { usePageViewContext, } from "../page-viewer/pageViewContext";
35
39
  import { QuickItemSwitcher } from "../QuickItemSwitcher";
40
+ import { useEditorSlotContext, } from "../views/editorSlotContext";
36
41
  import { getComments, getAvailableCommentTags, } from "../services/reviewsService";
37
42
  import { useReviews } from "../reviews/useReviews";
38
43
  import uuid from "react-uuid";
@@ -49,7 +54,38 @@ import { useWorkbox } from "./hooks/useWorkbox";
49
54
  import { useMediaSelector } from "./hooks/useMediaSelector";
50
55
  import { useGlobalEditorKeyDown } from "./hooks/useGlobalEditorKeyDown";
51
56
  import { useStartupChecks } from "../settings/status/index";
52
- export function EditorShell({ configuration, className, item: loadItemDescriptor, sessionId, userInfo, userPreferences, parheliaSettings, setUserPreferences, children, }) {
57
+ import { FeatureGate, LicenseFeatures, LicenseProvider, LicenseOverlay, } from "../../licensing";
58
+ // Sentinel written to the `sidebar` URL param when the user has explicitly closed
59
+ // every sidebar. Distinguishes "no preference yet" (param absent) from "user wants
60
+ // nothing open" (param present with this value) so reload can honor the user's intent.
61
+ const SIDEBAR_NONE_SENTINEL = "none";
62
+ const sidebarUrlValue = (ids) => ids.length ? ids.join(",") : SIDEBAR_NONE_SENTINEL;
63
+ function AgentsSlotContextBridge({ slot }) {
64
+ const editContext = useEditContext();
65
+ const slotContext = useEditorSlotContext({
66
+ slotId: slot.slotId,
67
+ itemDescriptor: slot.itemDescriptor,
68
+ refreshToken: slot.refreshToken,
69
+ });
70
+ if (!editContext || !slotContext)
71
+ return null;
72
+ useEffect(() => {
73
+ editContext.registerSlotContext(slot.slotId, slotContext);
74
+ return () => {
75
+ editContext.unregisterSlotContext(slot.slotId);
76
+ };
77
+ }, [
78
+ slot.slotId,
79
+ slotContext,
80
+ editContext.registerSlotContext,
81
+ editContext.unregisterSlotContext,
82
+ ]);
83
+ return null;
84
+ }
85
+ function AgentsSlotContextBridgeHost({ slots }) {
86
+ return (_jsx(_Fragment, { children: slots.map((slot) => (_jsx(AgentsSlotContextBridge, { slot: slot }, `agents-slot-bridge-${slot.slotId}`))) }));
87
+ }
88
+ export function EditorShell({ configuration, className, item: loadItemDescriptor, sessionId, userInfo, userPreferences, initialLicenseStatus, initialLicenseStatusLoaded, parheliaSettings, setUserPreferences, children, }) {
53
89
  const router = useRouter();
54
90
  const pathname = usePathname();
55
91
  const searchParams = useSearchParams();
@@ -97,6 +133,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
97
133
  const [historyMode, setHistoryMode] = useState("global");
98
134
  const [showOnlyMyChanges, setShowOnlyMyChanges] = useState(true);
99
135
  const [filterByCurrentLanguage, setFilterByCurrentLanguage] = useState(true);
136
+ const [historySearchQuery, setHistorySearchQuery] = useState("");
100
137
  const [recentEdits, setRecentEdits] = useState([]);
101
138
  const addRecentEdit = useCallback((edit) => {
102
139
  setRecentEdits((prevEditedFields) => {
@@ -124,14 +161,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
124
161
  if (typeof window !== "undefined")
125
162
  sessionStorage?.setItem("sessionId", sessionId);
126
163
  // Workspace state
127
- // Note: "reviews" is a sidebar, not a workspace. If view/workspace=reviews, we should
164
+ // Note: "reviews" is a sidebar, not a workspace. If workspace=reviews, we should
128
165
  // set workspace to "editor" and open the reviews sidebar instead.
129
166
  // Memoize searchParams reads to avoid triggering Router state updates during render
130
167
  // (Next.js App Router uses startTransition internally for URL changes)
131
- const rawWorkspace = useMemo(() => searchParams.get("workspace") ?? searchParams.get("view"), [searchParams]);
168
+ const rawWorkspace = useMemo(() => searchParams.get("workspace"), [searchParams]);
132
169
  const isReviewsSidebarRequest = rawWorkspace === "reviews";
133
170
  const [workspaceId, setWorkspaceId] = useState(
134
- // If view=reviews, use "editor" workspace (reviews is a sidebar, not a workspace)
171
+ // If workspace=reviews, use "editor" workspace (reviews is a sidebar, not a workspace)
135
172
  isReviewsSidebarRequest
136
173
  ? "editor"
137
174
  : (rawWorkspace ??
@@ -147,13 +184,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
147
184
  const [openSidebars, setOpenSidebars] = useState(() => {
148
185
  const sidebarParam = searchParams.get("sidebar");
149
186
  let sidebars = [];
150
- if (sidebarParam) {
151
- sidebars = sidebarParam.split(",").filter(Boolean);
187
+ // Presence check (not truthiness) so the sentinel "none" is respected across reloads.
188
+ if (sidebarParam !== null) {
189
+ sidebars =
190
+ sidebarParam === SIDEBAR_NONE_SENTINEL
191
+ ? []
192
+ : sidebarParam.split(",").filter(Boolean);
152
193
  }
153
194
  else {
154
195
  sidebars = [...(configuration.editor.defaultOpenSidebars ?? [])];
155
196
  }
156
- // If view/workspace=reviews was requested, ensure reviews sidebar is open
197
+ // If workspace=reviews was requested, ensure reviews sidebar is open
157
198
  if (isReviewsSidebarRequest && !sidebars.includes("reviews")) {
158
199
  sidebars.push("reviews");
159
200
  }
@@ -161,17 +202,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
161
202
  });
162
203
  // Toolbar pinned sidebars - icons always visible in toolbar (defined early so callbacks can access)
163
204
  const [pinnedSidebars, setPinnedSidebars] = useState(userInfo.preferences?.pinnedSidebars || []);
205
+ const pinnedSidebarsRef = useRef(pinnedSidebars);
164
206
  // Locked sidebars - open panels that stay visible when selecting another sidebar
165
207
  const [lockedSidebars, setLockedSidebars] = useState(() => {
166
- if (typeof window === "undefined") {
167
- return [];
168
- }
169
208
  try {
170
- const stored = window.localStorage.getItem("editor.lockedSidebars");
171
- if (!stored) {
172
- return [];
173
- }
174
- const parsed = JSON.parse(stored);
209
+ const parsed = localStorageService.getOrSetItem("editor.lockedSidebars", []);
175
210
  if (!Array.isArray(parsed) ||
176
211
  !parsed.every((id) => typeof id === "string")) {
177
212
  return [];
@@ -187,6 +222,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
187
222
  useEffect(() => {
188
223
  lockedSidebarsRef.current = lockedSidebars;
189
224
  }, [lockedSidebars]);
225
+ useEffect(() => {
226
+ pinnedSidebarsRef.current = pinnedSidebars;
227
+ }, [pinnedSidebars]);
190
228
  // Filter locked sidebars to only include currently open sidebars
191
229
  useEffect(() => {
192
230
  const openSet = new Set(openSidebars);
@@ -248,15 +286,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
248
286
  // Group open sidebars into vertical stacks (each stack shares one left column).
249
287
  const [sidebarStacks, setSidebarStacks] = useState(() => {
250
288
  const defaultStacks = openSidebars.map((id) => [id]);
251
- if (typeof window === "undefined") {
252
- return normalizeSidebarStacks(openSidebars, defaultStacks);
253
- }
254
289
  try {
255
- const stored = window.localStorage.getItem("editor.sidebarStacks");
256
- if (!stored) {
290
+ const parsed = localStorageService.getItem("editor.sidebarStacks");
291
+ if (!parsed) {
257
292
  return normalizeSidebarStacks(openSidebars, defaultStacks);
258
293
  }
259
- const parsed = JSON.parse(stored);
260
294
  if (!Array.isArray(parsed) ||
261
295
  !parsed.every((x) => Array.isArray(x) && x.every((id) => typeof id === "string"))) {
262
296
  return normalizeSidebarStacks(openSidebars, defaultStacks);
@@ -278,28 +312,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
278
312
  }, [openSidebars, normalizeSidebarStacks]);
279
313
  // Persist stacks independently of the URL (user layout preference).
280
314
  useEffect(() => {
281
- if (typeof window === "undefined")
282
- return;
283
315
  try {
284
- window.localStorage.setItem("editor.sidebarStacks", JSON.stringify(sidebarStacks));
316
+ localStorageService.setItem("editor.sidebarStacks", sidebarStacks);
285
317
  }
286
318
  catch { }
287
319
  }, [sidebarStacks]);
288
320
  // Persist locked sidebars to localStorage.
289
321
  useEffect(() => {
290
- if (typeof window === "undefined")
291
- return;
292
322
  try {
293
- window.localStorage.setItem("editor.lockedSidebars", JSON.stringify(lockedSidebars));
323
+ localStorageService.setItem("editor.lockedSidebars", lockedSidebars);
294
324
  }
295
325
  catch { }
296
326
  }, [lockedSidebars]);
297
- // Legacy aliases for backwards compatibility
298
327
  const viewName = workspaceId;
299
- const setViewName = setWorkspaceId;
300
- const viewNameRef = workspaceIdRef;
301
- const previousViewName = previousWorkspaceId;
302
- const setPreviousViewName = setPreviousWorkspaceId;
303
328
  const [compareMode, setCompareModeState] = useState(false);
304
329
  const [componentDesignerComponent, setComponentDesignerComponent] = useState();
305
330
  const [componentDesignerRendering, setComponentDesignerRendering] = useState();
@@ -307,6 +332,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
307
332
  const [ignoreBlur, setIgnoreBlur] = useState(false);
308
333
  const [currentItemDescriptor, setCurrentItemDescriptor] = useState();
309
334
  const currentItemDescriptorRef = useRef(undefined);
335
+ const latestGlobalLoadKeyRef = useRef(null);
310
336
  const [editorSlots, setEditorSlots] = useState(() => {
311
337
  // Always start with empty slots - don't persist the number of slots
312
338
  return [];
@@ -326,6 +352,29 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
326
352
  activeSlotIdRef.current = activeSlotId;
327
353
  }, [activeSlotId]);
328
354
  const [slotContexts, setSlotContexts] = useState(() => new Map());
355
+ const promptSessionReconnect = useCallback((reason) => {
356
+ const description = reason && reason !== "session-revoked"
357
+ ? `${reason}. Click reconnect to continue in this browser.`
358
+ : "You were disconnected because this account is active in another browser.";
359
+ toast.error("Session disconnected", {
360
+ id: "session-revoked",
361
+ description,
362
+ action: {
363
+ label: "Reconnect",
364
+ onClick: async () => {
365
+ const result = await reconnectSession(sessionId);
366
+ if (result.type === "success") {
367
+ window.location.reload();
368
+ return;
369
+ }
370
+ toast.error("Reconnect failed", {
371
+ description: "Could not claim this browser session. Try again.",
372
+ });
373
+ },
374
+ },
375
+ duration: Infinity,
376
+ });
377
+ }, [sessionId]);
329
378
  // Track previous item ID to detect actual navigation between pages
330
379
  const previousItemIdRef = useRef(undefined);
331
380
  useEffect(() => {
@@ -354,10 +403,18 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
354
403
  // Ref to track when we're handling a popstate event (browser back/forward)
355
404
  // This prevents the URL sync effect from pushing new history entries during back navigation
356
405
  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 : "");
406
+ // When switchWorkspace already pushed a URL entry, the follow-up URL sync
407
+ // effect should only *replace* that entry (not push a second one).
408
+ const switchWorkspacePushedRef = useRef(false);
409
+ // Ref to track the last known URL for the popstate handler.
410
+ // Uses pathname+search (not full href) so comparisons are consistent with the
411
+ // relative URLs produced by the URL sync effect and updateUrl.
412
+ const lastUrlRef = useRef(typeof window !== "undefined"
413
+ ? `${window.location.pathname}${window.location.search}`
414
+ : "");
415
+ // The very first URL sync after initial load should replaceState (not pushState)
416
+ // so the initial bare URL isn't left as a dead-end history entry.
417
+ const isFirstUrlSyncRef = useRef(true);
361
418
  const [inlineEditingFieldElement, setInlineEditingFieldElement] = useState();
362
419
  const [lockedField, setLockedField] = useState();
363
420
  const [itemLanguages, setItemLanguages] = useState([]);
@@ -370,26 +427,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
370
427
  const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
371
428
  const [availableCommentTags, setAvailableCommentTags] = useState([]);
372
429
  const [showComments, setShowComments] = useState(() => {
373
- const savedShowComments = typeof window !== "undefined"
374
- ? localStorage.getItem("editor.showComments")
375
- : null;
376
- return savedShowComments ? JSON.parse(savedShowComments) : true;
430
+ return localStorageService.getOrSetItem("editor.showComments", true);
377
431
  });
378
432
  useEffect(() => {
379
- if (typeof window !== "undefined") {
380
- localStorage.setItem("editor.showComments", JSON.stringify(showComments));
381
- }
433
+ localStorageService.setItem("editor.showComments", showComments);
382
434
  }, [showComments]);
383
435
  const [showResolvedComments, setShowResolvedComments] = useState(() => {
384
- const saved = typeof window !== "undefined"
385
- ? localStorage.getItem("editor.showResolvedComments")
386
- : null;
387
- return saved ? JSON.parse(saved) : false;
436
+ return localStorageService.getOrSetItem("editor.showResolvedComments", false);
388
437
  });
389
438
  useEffect(() => {
390
- if (typeof window !== "undefined") {
391
- localStorage.setItem("editor.showResolvedComments", JSON.stringify(showResolvedComments));
392
- }
439
+ localStorageService.setItem("editor.showResolvedComments", showResolvedComments);
393
440
  }, [showResolvedComments]);
394
441
  const [selectedComment, setSelectedComment] = useState();
395
442
  const [browseHistory, setBrowseHistory] = useState(() => {
@@ -408,13 +455,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
408
455
  visitedAt: entry.visitedAt,
409
456
  }));
410
457
  });
411
- // Navigation history for browser history (view + item combinations)
458
+ // Navigation history for browser history (workspace + item combinations)
412
459
  const [navigationHistory, setNavigationHistory] = useState(() => {
413
- // Initialize from browse history with current view
460
+ // Initialize from browse history with the current workspace
414
461
  if (!userInfo.browseHistory)
415
462
  return [];
416
463
  const defaultWorkspaceId = searchParams.get("workspace") ??
417
- searchParams.get("view") ??
418
464
  configuration.editor.defaultWorkspace ??
419
465
  configuration.editor.workspaces?.[0]?.id ??
420
466
  "editor";
@@ -446,20 +492,47 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
446
492
  const [statusMessage, setStatusMessage] = useState("");
447
493
  const [focusFieldComponentId, setFocusFieldComponentId] = useState();
448
494
  const [enableCompletions, setEnableCompletions] = useState(false);
449
- const [showComponentNavigator, setShowComponentNavigator] = useState(userPreferences.showComponentNavigator ?? false);
495
+ const [showComponentNavigatorDefault, setShowComponentNavigatorDefault] = useState(userPreferences.showComponentNavigator ?? false);
496
+ const [slotComponentNavigatorVisibility, setSlotComponentNavigatorVisibility] = useState({});
497
+ useEffect(() => {
498
+ setSlotComponentNavigatorVisibility((prev) => {
499
+ const next = {};
500
+ let changed = false;
501
+ for (const slot of editorSlots) {
502
+ if (Object.prototype.hasOwnProperty.call(prev, slot.slotId)) {
503
+ next[slot.slotId] = prev[slot.slotId];
504
+ }
505
+ else {
506
+ next[slot.slotId] = showComponentNavigatorDefault;
507
+ changed = true;
508
+ }
509
+ }
510
+ if (Object.keys(prev).length !== editorSlots.length) {
511
+ changed = true;
512
+ }
513
+ return changed ? next : prev;
514
+ });
515
+ }, [editorSlots, showComponentNavigatorDefault]);
450
516
  const [showAgentsPanel, setShowAgentsPanel] = useState(userPreferences.showAgentsPanel ?? false);
517
+ const [editorFormHidden, setEditorFormHiddenState] = useState(userPreferences.editorFormHidden ?? false);
518
+ const [editorFormHintSeen, setEditorFormHintSeenState] = useState(userPreferences.editorFormHintSeen ?? false);
519
+ const [editorFormHintVisible, setEditorFormHintVisible] = useState(false);
520
+ const editorFormToggleButtonRef = useRef(null);
451
521
  const [showMinimap, setShowMinimap] = useState(userPreferences.showMinimap ?? true);
452
522
  const [showHelpTerminal, setShowHelpTerminal] = useState(false);
453
523
  const [helpTerminalInitialPrompt, setHelpTerminalInitialPrompt] = useState(undefined);
454
524
  const [helpTerminalProfileName, setHelpTerminalProfileName] = useState(undefined);
455
525
  const [helpTerminalActiveTab, setHelpTerminalActiveTab] = useState(undefined);
456
- const [showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor] = useState(true);
526
+ const [selectedHelpSectionId, setSelectedHelpSectionId] = useState(null);
527
+ const [showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor] = useState(false);
528
+ const [selectedAgentsWorkspaceAgentId, setSelectedAgentsWorkspaceAgentId] = useState(null);
457
529
  const [activeEditorTab, setActiveEditorTab] = useState(null);
458
530
  const [showLayoutComponents, setShowLayoutComponents] = useState(userPreferences.showLayoutComponents ?? false);
459
531
  const { quotaInfo, setQuotaInfo, isQuotaExceeded, getQuotaWarningMessage } = useQuota({
460
532
  showError: ({ summary, details }) => showErrorToast({ summary, details }),
461
533
  });
462
534
  const [webSocketMessages, setWebSocketMessages] = useState([]);
535
+ const [socketDiagnostics, setSocketDiagnostics] = useState(() => createEditorSocketDiagnostics(sessionId));
463
536
  const [favorites, setFavorites] = useState([]);
464
537
  // Quick item switcher state
465
538
  const [quickSwitcherVisible, setQuickSwitcherVisible] = useState(false);
@@ -477,13 +550,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
477
550
  if (!startupChecks.hasBlockingIssues)
478
551
  return;
479
552
  // Don't redirect if already in settings workspace - let user navigate freely within settings
480
- const currentWorkspace = searchParams.get("workspace") ?? searchParams.get("view");
553
+ const currentWorkspace = searchParams.get("workspace");
481
554
  if (currentWorkspace === "settings")
482
555
  return;
483
556
  // Redirect to the status panel (where user can see all issues and navigate to fixes)
484
557
  const url = new URL(window.location.href);
485
558
  url.searchParams.set("workspace", "settings");
486
- url.searchParams.delete("view"); // Remove legacy param
487
559
  url.searchParams.set("ccpanel", "status");
488
560
  router.push(url.toString(), { scroll: false });
489
561
  }, [
@@ -501,6 +573,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
501
573
  setMode(queryMode);
502
574
  }
503
575
  }, [searchParams, isInitialLoad]);
576
+ useEffect(() => {
577
+ if (!isInitialLoad)
578
+ return;
579
+ const helpParam = searchParams.get("help");
580
+ if (helpParam) {
581
+ setHelpTerminalActiveTab("manual");
582
+ setShowHelpTerminal(true);
583
+ setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true" ? null : helpParam);
584
+ }
585
+ }, [searchParams, isInitialLoad]);
504
586
  useEffect(() => {
505
587
  if (mode === "suggestions") {
506
588
  // Ensure we're in the editor workspace and open the feedback sidebar
@@ -547,12 +629,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
547
629
  setSlotContexts((prev) => {
548
630
  if (!prev.has(slotId))
549
631
  return prev;
632
+ if (slotId === activeSlotIdRef.current) {
633
+ const activeCtx = prev.get(slotId);
634
+ if (activeCtx) {
635
+ lastActiveSlotContextRef.current = activeCtx;
636
+ }
637
+ }
550
638
  const next = new Map(prev);
551
639
  next.delete(slotId);
552
640
  return next;
553
641
  });
554
642
  }, []);
555
643
  const slotContextsRef = useRef(slotContexts);
644
+ const lastActiveSlotContextRef = useRef(null);
556
645
  useEffect(() => {
557
646
  slotContextsRef.current = slotContexts;
558
647
  }, [slotContexts]);
@@ -578,6 +667,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
578
667
  const activeSlotContext = activeSlotId
579
668
  ? slotContexts.get(activeSlotId)
580
669
  : undefined;
670
+ const isComponentNavigatorOpenForSlot = useCallback((slotId) => {
671
+ if (!slotId)
672
+ return showComponentNavigatorDefault;
673
+ return (slotComponentNavigatorVisibility[slotId] ?? showComponentNavigatorDefault);
674
+ }, [slotComponentNavigatorVisibility, showComponentNavigatorDefault]);
675
+ const showComponentNavigator = isComponentNavigatorOpenForSlot(activeSlotId);
581
676
  // Sync global compareMode from the active slot's compareMode for URL syncing
582
677
  useEffect(() => {
583
678
  if (activeSlotContext && activeSlotContext.compareMode !== compareMode) {
@@ -625,6 +720,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
625
720
  const isItemUsedInCurrentPage = useCallback((item) => pageItemsSetRef.current.has(makeItemKey(item)), [makeItemKey]);
626
721
  const socketMessageListeners = useRef(new Set());
627
722
  const [socketConnectionVersion, setSocketConnectionVersion] = useState(0);
723
+ useEffect(() => {
724
+ setSocketDiagnostics(createEditorSocketDiagnostics(sessionId));
725
+ }, [sessionId]);
628
726
  const addSocketMessageListener = useCallback((callback) => {
629
727
  socketMessageListeners.current.add(callback);
630
728
  return () => socketMessageListeners.current.delete(callback);
@@ -670,7 +768,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
670
768
  catch { }
671
769
  };
672
770
  }, [addSocketMessageListener]);
771
+ const shouldLoadReviews = openSidebars.includes("reviews") ||
772
+ workspaceId === "reviews" ||
773
+ workspaceId === "comments";
673
774
  const reviews = useReviews({
775
+ enabled: shouldLoadReviews,
674
776
  currentItemDescriptor,
675
777
  addSocketMessageListener: addSocketMessageListener,
676
778
  });
@@ -709,8 +811,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
709
811
  console.error(`No workspace found for id: ${workspaceId}`);
710
812
  return null;
711
813
  }
712
- // Legacy alias for backwards compatibility
713
- const currentView = currentWorkspace;
814
+ const activeKeyboardCommands = useMemo(() => {
815
+ const activeCommandIds = new Set(currentWorkspace.keyboardCommandIds ?? []);
816
+ return (configuration.commands.keyboardCommands ?? []).filter((command) => activeCommandIds.has(command.id));
817
+ }, [
818
+ currentWorkspace.keyboardCommandIds,
819
+ configuration.commands.keyboardCommands,
820
+ ]);
714
821
  useEffect(() => {
715
822
  if (currentWorkspace?.component) {
716
823
  setCenterPanelView(currentWorkspace.component);
@@ -738,6 +845,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
738
845
  const sendClientInfo = useCallback(() => {
739
846
  debouncedSendClientInfo();
740
847
  }, [debouncedSendClientInfo]);
848
+ const getCurrentHistoryState = useCallback(() => {
849
+ if (typeof window === "undefined") {
850
+ return null;
851
+ }
852
+ return window.history.state;
853
+ }, []);
854
+ useEffect(() => {
855
+ if (isInitialLoad) {
856
+ return;
857
+ }
858
+ const itemid = searchParams.get("itemid");
859
+ const language = searchParams.get("lang") ?? searchParams.get("language");
860
+ const versionParam = searchParams.get("version");
861
+ const version = versionParam ? parseInt(versionParam, 10) : 0;
862
+ const itemId = cleanId(itemid ?? undefined);
863
+ if (!itemId || !language) {
864
+ return;
865
+ }
866
+ const currentDescriptor = currentItemDescriptorRef.current;
867
+ const matchesCurrentDescriptor = currentDescriptor?.id === itemId &&
868
+ currentDescriptor?.language === language &&
869
+ (!version || currentDescriptor?.version === version);
870
+ if (matchesCurrentDescriptor) {
871
+ return;
872
+ }
873
+ isHandlingPopStateRef.current = true;
874
+ loadItemRef
875
+ .current({
876
+ id: itemId,
877
+ language,
878
+ version,
879
+ }, {
880
+ addToBrowseHistory: false,
881
+ skipViewChange: true,
882
+ })
883
+ .finally(() => {
884
+ isHandlingPopStateRef.current = false;
885
+ });
886
+ }, [isInitialLoad, searchParams]);
741
887
  const startTour = useCallback(() => {
742
888
  setIsTourActive(true);
743
889
  // Persist tour state to URL so it survives navigation/remounts
@@ -745,32 +891,61 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
745
891
  const params = new URLSearchParams(window.location.search);
746
892
  params.set("tour", "active");
747
893
  const newUrl = `${window.location.pathname}?${params.toString()}`;
748
- window.history.replaceState(null, "", newUrl);
894
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
749
895
  }, [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);
896
+ const isMobile = useMediaQuery("(max-width: 767px)");
897
+ // On mobile, clear all locked sidebars and keep only the last-opened panel.
898
+ // Handles viewport resize: desktop -> mobile unlocks everything and trims to one panel.
899
+ useEffect(() => {
900
+ if (isMobile) {
901
+ setLockedSidebars([]);
902
+ const current = openSidebarsRef.current;
903
+ const lastSidebar = current[current.length - 1];
904
+ if (current.length > 1 && lastSidebar) {
905
+ const trimmed = [lastSidebar];
906
+ openSidebarsRef.current = trimmed;
907
+ setOpenSidebars(trimmed);
908
+ }
909
+ }
910
+ }, [isMobile]);
911
+ const setComponentNavigatorOpenForSlot = useCallback((slotId, value) => {
912
+ const previousValue = isComponentNavigatorOpenForSlot(slotId);
913
+ const newValue = typeof value === "function" ? value(previousValue) : value;
914
+ if (slotId) {
915
+ setSlotComponentNavigatorVisibility((prev) => {
916
+ const currentValue = prev[slotId] ?? showComponentNavigatorDefault;
917
+ if (currentValue === newValue && prev[slotId] !== undefined) {
918
+ return prev;
919
+ }
920
+ return {
921
+ ...prev,
922
+ [slotId]: newValue,
923
+ };
924
+ });
925
+ }
926
+ setShowComponentNavigatorDefault(newValue);
754
927
  setUserPreferences({ showComponentNavigator: newValue });
755
- // On mobile, close Agents Panel when opening Component Navigator
756
928
  if (isMobile && newValue) {
757
929
  setShowAgentsPanel(false);
758
930
  setUserPreferences({ showAgentsPanel: false });
759
931
  }
760
932
  }, [
761
- showComponentNavigator,
762
- setShowComponentNavigator,
933
+ isComponentNavigatorOpenForSlot,
934
+ showComponentNavigatorDefault,
763
935
  setUserPreferences,
764
936
  isMobile,
765
937
  setShowAgentsPanel,
766
938
  ]);
939
+ const handleSetShowComponentNavigator = useCallback((value) => {
940
+ setComponentNavigatorOpenForSlot(activeSlotIdRef.current, value);
941
+ }, [setComponentNavigatorOpenForSlot]);
767
942
  const handleSetShowAgentsPanel = useCallback((value) => {
768
943
  const newValue = typeof value === "function" ? value(showAgentsPanel) : value;
769
944
  setShowAgentsPanel(newValue);
770
945
  setUserPreferences({ showAgentsPanel: newValue });
771
946
  // On mobile, close Component Navigator when opening Agents Panel
772
947
  if (isMobile && newValue) {
773
- setShowComponentNavigator(false);
948
+ setComponentNavigatorOpenForSlot(activeSlotIdRef.current, false);
774
949
  setUserPreferences({ showComponentNavigator: false });
775
950
  }
776
951
  }, [
@@ -778,13 +953,65 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
778
953
  setShowAgentsPanel,
779
954
  setUserPreferences,
780
955
  isMobile,
781
- setShowComponentNavigator,
956
+ setComponentNavigatorOpenForSlot,
782
957
  ]);
958
+ // Mobile editor panel state (EditorForm shown in bottom panel on mobile)
959
+ const [mobileEditorPanelOpen, setMobileEditorPanelOpenRaw] = useState(false);
960
+ const [dismissedMobilePanelToken, setDismissedMobilePanelToken] = useState(null);
961
+ const previousActiveSlotIdRef = useRef(null);
962
+ const mobilePanelDismissToken = useMemo(() => {
963
+ const selectionKey = selection.join(",");
964
+ return [
965
+ activeSlotId || "no-slot",
966
+ selectionKey,
967
+ insertMode ? "insert" : "browse",
968
+ ].join("|");
969
+ }, [activeSlotId, insertMode, selection]);
970
+ useEffect(() => {
971
+ const previousActiveSlotId = previousActiveSlotIdRef.current;
972
+ if (previousActiveSlotId !== activeSlotId) {
973
+ setDismissedMobilePanelToken(null);
974
+ }
975
+ previousActiveSlotIdRef.current = activeSlotId;
976
+ }, [activeSlotId]);
977
+ const handleSetMobileEditorPanelOpen = useCallback((open) => {
978
+ setMobileEditorPanelOpenRaw(open);
979
+ if (!open) {
980
+ setDismissedMobilePanelToken(mobilePanelDismissToken);
981
+ return;
982
+ }
983
+ setDismissedMobilePanelToken(null);
984
+ if (open && isMobile) {
985
+ // Close all sidebars when opening the editor panel on mobile
986
+ openSidebarsRef.current = [];
987
+ setOpenSidebars([]);
988
+ }
989
+ }, [isMobile, mobilePanelDismissToken]);
783
990
  const handleSetShowMinimap = useCallback((value) => {
784
991
  const newValue = typeof value === "function" ? value(showMinimap) : value;
785
992
  setShowMinimap(newValue);
786
993
  setUserPreferences({ showMinimap: newValue });
787
994
  }, [showMinimap, setUserPreferences]);
995
+ const handleSetEditorFormHidden = useCallback((value) => {
996
+ setEditorFormHiddenState(value);
997
+ setUserPreferences({ editorFormHidden: value });
998
+ }, [setUserPreferences]);
999
+ const markEditorFormHintSeen = useCallback(() => {
1000
+ setEditorFormHintSeenState(true);
1001
+ setUserPreferences({ editorFormHintSeen: true });
1002
+ }, [setUserPreferences]);
1003
+ const showEditorFormHint = useCallback(() => {
1004
+ setEditorFormHintVisible(true);
1005
+ }, []);
1006
+ const dismissEditorFormHint = useCallback(() => {
1007
+ setEditorFormHintVisible(false);
1008
+ setEditorFormHintSeenState((prev) => {
1009
+ if (prev)
1010
+ return prev;
1011
+ setUserPreferences({ editorFormHintSeen: true });
1012
+ return true;
1013
+ });
1014
+ }, [setUserPreferences]);
788
1015
  const handleSetShowHelpTerminal = useCallback((value) => {
789
1016
  const newValue = typeof value === "function" ? value(showHelpTerminal) : value;
790
1017
  setShowHelpTerminal(newValue);
@@ -793,6 +1020,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
793
1020
  setHelpTerminalInitialPrompt(undefined);
794
1021
  setHelpTerminalProfileName(undefined);
795
1022
  setHelpTerminalActiveTab(undefined);
1023
+ setSelectedHelpSectionId(null);
796
1024
  }
797
1025
  }, [showHelpTerminal]);
798
1026
  const toggleHelpTerminal = useCallback((options) => {
@@ -835,13 +1063,27 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
835
1063
  setOpenSidebars(newOrder);
836
1064
  setSidebarStacks((prev) => normalizeSidebarStacks(newOrder, prev));
837
1065
  }, []);
1066
+ const ensureSidebarPinned = useCallback((sidebarId) => {
1067
+ const currentPinnedSidebars = pinnedSidebarsRef.current;
1068
+ if (currentPinnedSidebars.includes(sidebarId)) {
1069
+ return;
1070
+ }
1071
+ const newPinnedSidebars = [...currentPinnedSidebars, sidebarId];
1072
+ pinnedSidebarsRef.current = newPinnedSidebars;
1073
+ setPinnedSidebars(newPinnedSidebars);
1074
+ setUserPreferences({ pinnedSidebars: newPinnedSidebars });
1075
+ }, [setUserPreferences]);
838
1076
  // messageHandler is defined after loadItem/loadHistory declarations to avoid temporal dead zones
839
1077
  const user = activeSessions.find((x) => x.sessionId === sessionId)?.user ||
840
1078
  userInfo.user;
841
- // Self-heal if our session disappears (e.g., after HMR)
1079
+ // Self-heal if our session disappears (e.g., after HMR). Skip when concurrent user limit
1080
+ // is showing so we don't spam recovery attempts when the connection was rejected.
842
1081
  const missingSessionRecoveryTimerRef = useRef(null);
843
1082
  const missingSessionRecoveryAttemptsRef = useRef(0);
1083
+ const concurrentUserLimitErrorRef = useRef(null);
844
1084
  useEffect(() => {
1085
+ if (concurrentUserLimitErrorRef.current !== null)
1086
+ return;
845
1087
  const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
846
1088
  if (hasMySession) {
847
1089
  // Reset recovery state when we see ourselves again
@@ -857,7 +1099,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
857
1099
  return;
858
1100
  const attempt = missingSessionRecoveryAttemptsRef.current + 1;
859
1101
  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
1102
  missingSessionRecoveryTimerRef.current = setTimeout(() => {
862
1103
  missingSessionRecoveryTimerRef.current = null;
863
1104
  missingSessionRecoveryAttemptsRef.current = attempt;
@@ -866,6 +1107,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
866
1107
  }
867
1108
  else {
868
1109
  // Force a reconnect to refresh presence after several failed nudges
1110
+ console.warn("Session presence did not recover after retries. Forcing reconnect.");
869
1111
  try {
870
1112
  globalThis.editorSocket?.close(4000, "recover-presence");
871
1113
  }
@@ -885,22 +1127,56 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
885
1127
  // Initialize lastUrlRef to current URL on mount
886
1128
  lastUrlRef.current = window.location.href;
887
1129
  const keepAliveUrl = "/parhelia/keepalive";
888
- const interval = setInterval(() => {
889
- fetch(keepAliveUrl + "?ts=" + Date.now())
1130
+ const runSessionCheck = () => {
1131
+ fetch(keepAliveUrl + "?ts=" + Date.now(), {
1132
+ credentials: "include",
1133
+ headers: { "Cache-Control": "no-cache" },
1134
+ })
890
1135
  .then((response) => {
891
- if (response.status === 401 || response.status === 403) {
892
- toast.error("Your session has expired", {
893
- description: "Please login again to continue editing.",
894
- action: {
895
- label: "Login",
896
- onClick: () => (window.location.href = "/sitecore/login"),
1136
+ if (response.headers.get("X-Parhelia-Session-Revoked") === "true") {
1137
+ window.dispatchEvent(new CustomEvent("parhelia:session-revoked", {
1138
+ detail: { reason: "session-revoked" },
1139
+ }));
1140
+ return;
1141
+ }
1142
+ // A redirected response (e.g. to /sitecore/login) or an explicit
1143
+ // 401/403 both mean the user is no longer authenticated. Let a
1144
+ // single event-driven toast handle the UI.
1145
+ const finalUrl = response.url || "";
1146
+ const redirectedToLogin = response.redirected &&
1147
+ (/\/sitecore\/login/i.test(finalUrl) ||
1148
+ /[?&]returnUrl=/i.test(finalUrl));
1149
+ if (redirectedToLogin ||
1150
+ response.status === 401 ||
1151
+ response.status === 403) {
1152
+ window.dispatchEvent(new CustomEvent("parhelia:session-expired", {
1153
+ detail: {
1154
+ reason: redirectedToLogin
1155
+ ? "login-redirect"
1156
+ : `status-${response.status}`,
897
1157
  },
898
- duration: Infinity,
899
- });
1158
+ }));
900
1159
  }
901
1160
  })
902
1161
  .catch((error) => console.error("Keep Alive error:", error));
903
- }, 5 * 60 * 1000);
1162
+ };
1163
+ const keepaliveIntervalMs = (() => {
1164
+ const param = new URLSearchParams(window.location.search).get("keepaliveIntervalMs");
1165
+ if (!param)
1166
+ return 5 * 60 * 1000;
1167
+ const ms = parseInt(param, 10);
1168
+ return Number.isFinite(ms) && ms >= 2000 && ms <= 60000
1169
+ ? ms
1170
+ : 5 * 60 * 1000;
1171
+ })();
1172
+ const interval = setInterval(() => {
1173
+ runSessionCheck();
1174
+ }, keepaliveIntervalMs);
1175
+ const handleVisibilityChange = () => {
1176
+ if (document.visibilityState === "visible") {
1177
+ runSessionCheck();
1178
+ }
1179
+ };
904
1180
  const handleMessage = (event) => {
905
1181
  if (event.data.type === "componentsSelected") {
906
1182
  setSelection(event.data.componentIds);
@@ -908,35 +1184,44 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
908
1184
  };
909
1185
  // Listen for browser navigation events (back/forward buttons)
910
1186
  const handlePopState = (event) => {
911
- const newUrl = window.location.href;
1187
+ const newUrl = `${window.location.pathname}${window.location.search}`;
912
1188
  if (newUrl !== lastUrlRef.current) {
913
1189
  lastUrlRef.current = newUrl;
914
1190
  // Mark that we're handling a popstate to prevent URL sync from pushing to history
915
1191
  isHandlingPopStateRef.current = true;
916
1192
  // Sync URL parameters back to component state for browser navigation
917
1193
  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) {
1194
+ const urlWorkspace = urlParams.get("workspace");
1195
+ if (urlWorkspace && urlWorkspace !== workspaceIdRef.current) {
921
1196
  setWorkspaceId(urlWorkspace);
922
1197
  }
923
- // Handle sidebar changes
1198
+ // Handle sidebar changes — clear when absent so state matches the URL
924
1199
  const sidebarParam = urlParams.get("sidebar");
925
- if (sidebarParam !== null) {
926
- const newSidebars = sidebarParam.split(",").filter(Boolean);
927
- setOpenSidebars(newSidebars);
928
- }
1200
+ const newSidebars = sidebarParam
1201
+ ? sidebarParam.split(",").filter(Boolean)
1202
+ : [];
1203
+ setOpenSidebars(newSidebars);
929
1204
  // Handle wizard ID changes
930
1205
  const wizardId = urlParams.get("wizardid");
931
1206
  setCurrentWizardId(wizardId);
932
- // Handle compare mode changes
933
- const compareValue = urlParams.get("compare") === "true";
934
- if (compareValue !== compareMode) {
935
- setCompareMode(compareValue);
1207
+ // Handle compare mode changes — always set to avoid stale-closure mismatch
1208
+ // (React skips re-render when the value is unchanged)
1209
+ setCompareMode(urlParams.get("compare") === "true");
1210
+ // Handle help panel changes
1211
+ const helpParam = urlParams.get("help");
1212
+ if (helpParam) {
1213
+ setHelpTerminalActiveTab("manual");
1214
+ setShowHelpTerminal(true);
1215
+ setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true"
1216
+ ? null
1217
+ : helpParam);
936
1218
  }
937
- // Handle mode changes
1219
+ else {
1220
+ handleSetShowHelpTerminal(false);
1221
+ }
1222
+ // Handle mode changes — always set to avoid stale-closure mismatch
938
1223
  const urlMode = urlParams.get("mode");
939
- if (urlMode && urlMode !== mode) {
1224
+ if (urlMode) {
940
1225
  setMode(urlMode);
941
1226
  }
942
1227
  // Handle fullscreen changes (shell-level state)
@@ -989,9 +1274,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
989
1274
  };
990
1275
  window.addEventListener("message", handleMessage);
991
1276
  window.addEventListener("popstate", handlePopState);
1277
+ window.addEventListener("visibilitychange", handleVisibilityChange);
992
1278
  return () => {
993
1279
  window.removeEventListener("message", handleMessage);
994
1280
  window.removeEventListener("popstate", handlePopState);
1281
+ window.removeEventListener("visibilitychange", handleVisibilityChange);
995
1282
  clearInterval(interval);
996
1283
  };
997
1284
  }, []);
@@ -1001,14 +1288,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1001
1288
  if (searchParams.get("noTour") !== null) {
1002
1289
  return;
1003
1290
  }
1291
+ // Don't start tour when there are setup errors (user is or will be on system status page)
1292
+ if (startupChecks.state === "complete" && startupChecks.hasBlockingIssues) {
1293
+ return;
1294
+ }
1295
+ // Don't start tour when already on settings system status page
1296
+ if (viewName === "settings" && searchParams.get("ccpanel") === "status") {
1297
+ return;
1298
+ }
1299
+ // Wait for startup checks so we know whether we'll redirect to status
1300
+ if (startupChecks.state !== "complete") {
1301
+ return;
1302
+ }
1004
1303
  const tour = configuration.activeTour;
1005
1304
  const key = tour === "default" ? "editor.tourShown" : "editor.tourShown." + tour;
1006
- const tourShown = localStorage.getItem(key);
1305
+ const tourShown = localStorageService.getString(key);
1007
1306
  if (!tourShown) {
1008
1307
  startTour();
1009
- localStorage.setItem(key, "true");
1308
+ localStorageService.setString(key, "true");
1010
1309
  }
1011
- }, [user]);
1310
+ }, [
1311
+ user,
1312
+ startupChecks.state,
1313
+ startupChecks.hasBlockingIssues,
1314
+ viewName,
1315
+ searchParams,
1316
+ configuration.activeTour,
1317
+ startTour,
1318
+ ]);
1012
1319
  // WebSocket initialization is performed after messageHandler is defined
1013
1320
  // Defer URL sync until loadItem is defined below
1014
1321
  // Mark end of initial load phase
@@ -1140,7 +1447,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1140
1447
  const loadComments = useCallback(async () => {
1141
1448
  if (!currentItemDescriptor)
1142
1449
  return;
1143
- const result = await getComments(currentItemDescriptor.id, currentItemDescriptor.language, currentItemDescriptor.version);
1450
+ const reviewId = searchParams.get("reviewId");
1451
+ const result = await getComments(currentItemDescriptor.id, currentItemDescriptor.language, currentItemDescriptor.version, reviewId ?? undefined);
1144
1452
  if (handleErrorResult(result, ui, state))
1145
1453
  return;
1146
1454
  setComments((x) => {
@@ -1153,7 +1461,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1153
1461
  allComments.sort((a, b) => a.position - b.position);
1154
1462
  return allComments;
1155
1463
  });
1156
- }, [currentItemDescriptor]);
1464
+ }, [currentItemDescriptor, searchParams]);
1157
1465
  // Assuming currentItemDescriptor, ui, state, handleErrorResult, and setSuggestedEdits
1158
1466
  // are available in your component context.
1159
1467
  const loadSuggestedEdits = useCallback(async () => {
@@ -1162,7 +1470,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1162
1470
  const result = await getSuggestedEdits(item.descriptor.id, item.descriptor.language, item.descriptor.version);
1163
1471
  if (handleErrorResult(result, ui, state))
1164
1472
  return;
1165
- setSuggestedEdits(result.data || []);
1473
+ const edits = result.data || [];
1474
+ setSuggestedEdits(edits);
1166
1475
  }, [item]);
1167
1476
  const loadFavorites = useCallback(async () => {
1168
1477
  try {
@@ -1236,6 +1545,25 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1236
1545
  return idB.localeCompare(idA);
1237
1546
  });
1238
1547
  }, []);
1548
+ const normalizeEditHistoryPayload = useCallback((value) => {
1549
+ if (Array.isArray(value)) {
1550
+ return value;
1551
+ }
1552
+ if (value && typeof value === "object") {
1553
+ const payload = value;
1554
+ const candidates = [
1555
+ payload.operations,
1556
+ payload.history,
1557
+ payload.items,
1558
+ payload.data,
1559
+ ];
1560
+ const firstArray = candidates.find((candidate) => Array.isArray(candidate));
1561
+ if (Array.isArray(firstArray)) {
1562
+ return firstArray;
1563
+ }
1564
+ }
1565
+ return [];
1566
+ }, []);
1239
1567
  useEffect(() => {
1240
1568
  // Read fresh page from the mutable slot context ref chain.
1241
1569
  // The slot context uses a stable ref that is mutated in-place (see editorSlotContext.ts),
@@ -1258,7 +1586,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1258
1586
  if (handleErrorResult(result, ui, state))
1259
1587
  return;
1260
1588
  setEditHistory((prev) => {
1261
- const next = result.data || [];
1589
+ const next = normalizeEditHistoryPayload(result.data);
1262
1590
  if (!prev.length)
1263
1591
  return sortEditHistoryByDateDesc(next);
1264
1592
  if (!next.length)
@@ -1317,6 +1645,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1317
1645
  const shouldFilterByLanguage = filterByLanguage !== undefined
1318
1646
  ? filterByLanguage
1319
1647
  : filterByCurrentLanguage;
1648
+ const trimmedHistoryQuery = historySearchQuery.trim();
1649
+ const historyQuery = trimmedHistoryQuery.length > 0 ? trimmedHistoryQuery : undefined;
1320
1650
  if (currentMode === "global") {
1321
1651
  // Global mode: optionally filter by session and/or language
1322
1652
  const currentLanguage = item?.descriptor?.language || currentItemDescriptor?.language;
@@ -1326,13 +1656,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1326
1656
  language: shouldFilterByLanguage && currentLanguage
1327
1657
  ? currentLanguage
1328
1658
  : undefined,
1659
+ query: historyQuery,
1329
1660
  });
1330
1661
  if (handleErrorResult(result, ui, state)) {
1331
1662
  console.error("[EditorShell] Failed to load history:", result);
1332
1663
  return;
1333
1664
  }
1334
1665
  setEditHistory((prev) => {
1335
- const next = result.data || [];
1666
+ const next = normalizeEditHistoryPayload(result.data);
1336
1667
  const scope = {
1337
1668
  mode: currentMode,
1338
1669
  filterBySession: shouldFilterBySession,
@@ -1343,8 +1674,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1343
1674
  };
1344
1675
  if (!prev.length)
1345
1676
  return sortEditHistoryByDateDesc(next.filter((op) => matchesHistoryScope(op, scope)));
1346
- if (!next.length)
1677
+ if (!next.length) {
1678
+ // When searching, respect the empty result — don't fall back to previous items
1679
+ if (historyQuery)
1680
+ return [];
1347
1681
  return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
1682
+ }
1348
1683
  const prevById = new Map(prev.map((x) => [x.id, x]));
1349
1684
  const nextById = new Set(next.map((x) => x.id));
1350
1685
  const merged = next
@@ -1380,9 +1715,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1380
1715
  return mergedOp;
1381
1716
  });
1382
1717
  // 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);
1718
+ // but not when filtering by search query — search results are authoritative
1719
+ if (!historyQuery) {
1720
+ for (const [id, priorOp] of prevById) {
1721
+ if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1722
+ merged.push(priorOp);
1723
+ }
1386
1724
  }
1387
1725
  }
1388
1726
  return sortEditHistoryByDateDesc(merged);
@@ -1405,12 +1743,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1405
1743
  const result = await getEditHistory({
1406
1744
  item: itemFilter,
1407
1745
  sessionId: shouldFilterBySession ? sessionId : undefined,
1746
+ query: historyQuery,
1408
1747
  });
1409
1748
  if (handleErrorResult(result, ui, state)) {
1410
1749
  console.error("[EditorShell] Failed to load item history:", result);
1411
1750
  return;
1412
1751
  }
1413
- let operations = result.data || [];
1752
+ let operations = normalizeEditHistoryPayload(result.data);
1414
1753
  // Client-side version filtering for current-version mode
1415
1754
  if (currentMode === "current-version") {
1416
1755
  // Defensive filter: only include operations for the current version
@@ -1433,8 +1772,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1433
1772
  };
1434
1773
  if (!prev.length)
1435
1774
  return sortEditHistoryByDateDesc(operations.filter((op) => matchesHistoryScope(op, scope)));
1436
- if (!operations.length)
1775
+ if (!operations.length) {
1776
+ if (historyQuery)
1777
+ return [];
1437
1778
  return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
1779
+ }
1438
1780
  const prevById = new Map(prev.map((x) => [x.id, x]));
1439
1781
  const nextById = new Set(operations.map((x) => x.id));
1440
1782
  const merged = operations
@@ -1470,9 +1812,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1470
1812
  return mergedOp;
1471
1813
  });
1472
1814
  // 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);
1815
+ // but not when filtering by search query — search results are authoritative
1816
+ if (!historyQuery) {
1817
+ for (const [id, priorOp] of prevById) {
1818
+ if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
1819
+ merged.push(priorOp);
1820
+ }
1476
1821
  }
1477
1822
  }
1478
1823
  return sortEditHistoryByDateDesc(merged);
@@ -1483,9 +1828,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1483
1828
  historyMode,
1484
1829
  showOnlyMyChanges,
1485
1830
  filterByCurrentLanguage,
1831
+ historySearchQuery,
1486
1832
  item,
1487
1833
  currentItemDescriptor,
1488
1834
  matchesHistoryScope,
1835
+ normalizeEditHistoryPayload,
1489
1836
  sortEditHistoryByDateDesc,
1490
1837
  ]);
1491
1838
  // Debounced history refresh to avoid hammering `/parhelia/editHistory` on rapid UI changes.
@@ -1551,13 +1898,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1551
1898
  }
1552
1899
  }
1553
1900
  else if (historyMode !== "global" && currentItemDescriptor) {
1901
+ // Always load immediately for page-centric/current-version/timeline so we don't show
1902
+ // an empty history list for the debounce window (e.g. 600ms). The effect only runs on
1903
+ // mode or item id/lang/version change, not on every keystroke, so this avoids flaky
1904
+ // undo/redo tests and improves UX when switching to page-centric.
1554
1905
  if (!historyInitialLoadDoneRef.current) {
1555
1906
  historyInitialLoadDoneRef.current = true;
1556
- refreshHistoryRef.current(historyMode);
1557
- }
1558
- else {
1559
- debouncedRefreshHistoryRef.current(historyMode);
1560
1907
  }
1908
+ refreshHistoryRef.current(historyMode);
1561
1909
  }
1562
1910
  else if (historyMode !== "global" && !currentItemDescriptor) {
1563
1911
  // Clear history if no item loaded in page-centric modes
@@ -1570,6 +1918,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1570
1918
  historyMode,
1571
1919
  showOnlyMyChanges,
1572
1920
  filterByCurrentLanguage,
1921
+ historySearchQuery,
1573
1922
  currentItemDescriptor?.id,
1574
1923
  currentItemDescriptor?.language,
1575
1924
  currentItemDescriptor?.version,
@@ -1634,6 +1983,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1634
1983
  if (isInitialLoad)
1635
1984
  return;
1636
1985
  const current = new URLSearchParams(window.location.search);
1986
+ const urlWorkspace = current.get("workspace");
1987
+ const urlView = current.get("view");
1988
+ const isWorkspaceTransitioning = !!urlWorkspace && urlWorkspace !== viewName;
1637
1989
  // Sync item-related parameters only when an item is selected
1638
1990
  if (currentItemDescriptor) {
1639
1991
  if (current.get("itemid") !== currentItemDescriptor.id) {
@@ -1664,20 +2016,21 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1664
2016
  // If reviewId or urlItemId exists, preserve itemid/lang/version from URL
1665
2017
  }
1666
2018
  // Always sync workspace-related parameters regardless of item selection
1667
- if (current.get("workspace") !== viewName) {
2019
+ if (!isWorkspaceTransitioning && current.get("workspace") !== viewName) {
1668
2020
  current.set("workspace", viewName);
1669
2021
  }
1670
- current.delete("view"); // Remove legacy view param
1671
- // Sync sidebar state
2022
+ // Sync sidebar state. Always write a value (sentinel when empty) so that an
2023
+ // explicit "user closed everything" state survives a reload.
1672
2024
  const currentSidebars = current.get("sidebar") ?? "";
1673
- const newSidebars = openSidebars.join(",");
2025
+ const newSidebars = sidebarUrlValue(openSidebars);
1674
2026
  if (currentSidebars !== newSidebars) {
1675
- if (newSidebars) {
1676
- current.set("sidebar", newSidebars);
1677
- }
1678
- else {
1679
- current.delete("sidebar");
1680
- }
2027
+ current.set("sidebar", newSidebars);
2028
+ }
2029
+ if (showHelpTerminal) {
2030
+ current.set("help", selectedHelpSectionId ?? "contents");
2031
+ }
2032
+ else {
2033
+ current.delete("help");
1681
2034
  }
1682
2035
  if (!compareMode) {
1683
2036
  current.delete("compare");
@@ -1703,15 +2056,15 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1703
2056
  else {
1704
2057
  current.delete("wizardid");
1705
2058
  }
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 {
2059
+ // Preserve settings-specific parameters while transitioning into Settings too.
2060
+ // Some callers update the URL first and switch the workspace state a moment later.
2061
+ const isSettingsNavigation = viewName === "settings" ||
2062
+ urlWorkspace === "settings" ||
2063
+ urlView === "settings";
2064
+ if (!isSettingsNavigation) {
1714
2065
  current.delete("ccpanel");
2066
+ current.delete("providerId");
2067
+ current.delete("modelId");
1715
2068
  }
1716
2069
  // Preserve reviewId parameter if it exists (for review links)
1717
2070
  // This ensures review links don't lose the reviewId when URL is synced
@@ -1724,17 +2077,42 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1724
2077
  const browserPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
1725
2078
  const newUrl = `${browserPathname}?${current.toString()}`;
1726
2079
  const oldUrl = `${browserPathname}${window.location.search}`;
2080
+ const isRestoringLastSyncedUrl = newUrl === lastUrlRef.current;
1727
2081
  // Skip pushing to history if we're handling a popstate event (browser back/forward)
1728
2082
  // This prevents the URL sync from overwriting the history during back navigation
1729
2083
  if (isHandlingPopStateRef.current) {
1730
2084
  return;
1731
2085
  }
1732
2086
  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
2087
+ if (isFirstUrlSyncRef.current ||
2088
+ switchWorkspacePushedRef.current ||
2089
+ isRestoringLastSyncedUrl) {
2090
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
2091
+ isFirstUrlSyncRef.current = false;
2092
+ switchWorkspacePushedRef.current = false;
2093
+ }
2094
+ else {
2095
+ window.history.pushState(getCurrentHistoryState(), "", newUrl);
2096
+ }
1736
2097
  lastUrlRef.current = newUrl;
1737
2098
  }
2099
+ else {
2100
+ // Even when the first sync is a no-op (URL already matches state), consume
2101
+ // the first-sync flag. Otherwise the *next* real change (e.g. user selecting
2102
+ // a different item) would hit the replaceState branch and overwrite the
2103
+ // landing history entry — eating the "back" target and making browser back
2104
+ // skip past the initial page.
2105
+ if (isFirstUrlSyncRef.current) {
2106
+ isFirstUrlSyncRef.current = false;
2107
+ lastUrlRef.current = newUrl;
2108
+ }
2109
+ if (switchWorkspacePushedRef.current) {
2110
+ // A workspace change may already have pushed the exact target URL. If we leave
2111
+ // this flag set, the next unrelated item navigation will incorrectly replace
2112
+ // history instead of pushing a new entry, which breaks browser back/forward.
2113
+ switchWorkspacePushedRef.current = false;
2114
+ }
2115
+ }
1738
2116
  }, [
1739
2117
  currentItemDescriptor,
1740
2118
  viewName,
@@ -1746,6 +2124,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1746
2124
  fullscreen,
1747
2125
  currentWizardId,
1748
2126
  openSidebars,
2127
+ showHelpTerminal,
2128
+ selectedHelpSectionId,
1749
2129
  ]);
1750
2130
  useEffect(() => {
1751
2131
  async function load() {
@@ -1842,12 +2222,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1842
2222
  "en",
1843
2223
  version: 0,
1844
2224
  };
1845
- const loadedItem = await itemsRepository.getItem(itemToLoad);
1846
- if (!loadedItem) {
1847
- return undefined;
1848
- }
1849
2225
  // ensure this is object has no additional properties
1850
2226
  itemToLoad = getItemDescriptor(itemToLoad);
2227
+ const requestedItemKey = makeItemKey(itemToLoad);
1851
2228
  // Check if item is already open in any slot - if so, reuse that slot instead of creating a new one
1852
2229
  const existingSlotForItem = editorSlots.find((s) => s.itemDescriptor.id === itemToLoad.id &&
1853
2230
  s.itemDescriptor.language === itemToLoad.language &&
@@ -1858,6 +2235,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1858
2235
  !options?.openInNewSlot &&
1859
2236
  !options?.targetSlotId) {
1860
2237
  const isExistingSlotActive = existingSlotForItem.slotId === activeSlotIdRef.current;
2238
+ if (isExistingSlotActive) {
2239
+ latestGlobalLoadKeyRef.current = requestedItemKey;
2240
+ }
2241
+ const loadedItem = await itemsRepository.getItem(itemToLoad);
2242
+ if (!loadedItem) {
2243
+ return undefined;
2244
+ }
2245
+ if (isExistingSlotActive &&
2246
+ latestGlobalLoadKeyRef.current !== requestedItemKey) {
2247
+ return loadedItem;
2248
+ }
1861
2249
  // If forceRefresh is true, update the slot with a new refreshToken to trigger a reload
1862
2250
  if (options?.forceRefresh) {
1863
2251
  setEditorSlots((prev) => {
@@ -1928,12 +2316,30 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1928
2316
  return next;
1929
2317
  });
1930
2318
  // Decide whether this load should update the global "current item" state.
1931
- // - If we're loading into an inactive slot (e.g. openInNewSlot), do NOT update URL/tree/controls.
1932
- // - If the slot is active (or is becoming active as the first slot), do update.
2319
+ // - If we're loading into an inactive slot we won't normally touch, do NOT update URL/tree/controls.
2320
+ // - If the slot is active, becoming active as the first slot, or is a brand-new
2321
+ // slot the caller explicitly opened, do update.
1933
2322
  const isFirstSlot = editorSlots.length === 0;
1934
- const shouldUpdateGlobalCurrentItem = isFirstSlot || targetSlotId === activeSlotIdRef.current;
1935
- // Active slot is controlled ONLY by mouse hover - only activate the first slot on initial load
1936
- if (isFirstSlot) {
2323
+ const isExplicitNewSlot = options?.openInNewSlot === true && !options?.targetSlotId;
2324
+ const shouldUpdateGlobalCurrentItem = isFirstSlot ||
2325
+ isExplicitNewSlot ||
2326
+ targetSlotId === activeSlotIdRef.current;
2327
+ if (shouldUpdateGlobalCurrentItem) {
2328
+ latestGlobalLoadKeyRef.current = requestedItemKey;
2329
+ }
2330
+ const loadedItem = await itemsRepository.getItem(itemToLoad);
2331
+ if (!loadedItem) {
2332
+ return undefined;
2333
+ }
2334
+ if (shouldUpdateGlobalCurrentItem &&
2335
+ latestGlobalLoadKeyRef.current !== requestedItemKey) {
2336
+ return loadedItem;
2337
+ }
2338
+ // Activate the new slot when this is either the initial first slot, or the
2339
+ // caller explicitly opened the item in a new slot — focus follows the action so
2340
+ // the user immediately sees what they just opened. Beyond that, slot focus is
2341
+ // driven by mouse hover/click.
2342
+ if (isFirstSlot || isExplicitNewSlot) {
1937
2343
  activeSlotIdRef.current = targetSlotId;
1938
2344
  setActiveSlotId(targetSlotId);
1939
2345
  }
@@ -2039,9 +2445,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2039
2445
  useEffect(() => {
2040
2446
  if (fullscreen &&
2041
2447
  !searchParams.get("fullscreen") &&
2042
- !configuration.forceFullscreen)
2448
+ !configuration.forceFullscreen &&
2449
+ !isMobile)
2043
2450
  setShowFullscreenHint(true);
2044
- }, [fullscreen, configuration.forceFullscreen, searchParams]);
2451
+ }, [fullscreen, configuration.forceFullscreen, searchParams, isMobile]);
2045
2452
  const state = {
2046
2453
  page,
2047
2454
  configuration,
@@ -2140,20 +2547,35 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2140
2547
  ? existingOp.progress
2141
2548
  : op.progress;
2142
2549
  // 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;
2550
+ // UNLESS this update indicates the operation was undone.
2551
+ // Runtime logs show long-running undo completion can arrive as:
2552
+ // - existing: canUndo=true, executionStatus=executing
2553
+ // - incoming: canUndo=false, executionStatus=completed
2554
+ // without explicit undone/canRedo flags yet.
2555
+ const isExplicitUndoUpdate = op.undone === true || op.canRedo === true;
2556
+ const isInferredUndoCompletion = existingOp.executionStatus === "executing" &&
2557
+ op.executionStatus === "completed" &&
2558
+ existingOp.canUndo === true &&
2559
+ op.canUndo === false;
2560
+ const isUndoUpdate = isExplicitUndoUpdate || isInferredUndoCompletion;
2147
2561
  const mergedCanUndo = isUndoUpdate
2148
2562
  ? false
2149
2563
  : existingOp.canUndo === true
2150
2564
  ? true
2151
2565
  : op.canUndo;
2566
+ const mergedCanRedo = isUndoUpdate
2567
+ ? true
2568
+ : op.canRedo ?? existingOp.canRedo;
2569
+ const mergedUndone = isUndoUpdate
2570
+ ? true
2571
+ : op.undone ?? existingOp.undone;
2152
2572
  const mergedOp = {
2153
2573
  ...existingOp,
2154
2574
  ...op,
2155
2575
  progress: mergedProgress,
2156
2576
  canUndo: mergedCanUndo,
2577
+ canRedo: mergedCanRedo,
2578
+ undone: mergedUndone,
2157
2579
  };
2158
2580
  // Ensure undone operations always have canRedo: true.
2159
2581
  if (mergedOp.undone && !mergedOp.canRedo) {
@@ -2207,10 +2629,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2207
2629
  });
2208
2630
  // Ref for markOperationComplete callback (needed because operationsContext is created later)
2209
2631
  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
2632
  // WebSocket message handler and connection
2215
2633
  const messageHandler = useSocketMessageHandler({
2216
2634
  sessionId,
@@ -2236,28 +2654,31 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2236
2654
  markOperationCompleteRef.current?.(operationId);
2237
2655
  },
2238
2656
  });
2657
+ // Concurrent user limit error state
2658
+ const [concurrentUserLimitError, setConcurrentUserLimitError] = useState(null);
2659
+ concurrentUserLimitErrorRef.current = concurrentUserLimitError;
2660
+ const handleRetryConnection = useCallback(() => {
2661
+ setConcurrentUserLimitError(null);
2662
+ // Force reconnection by triggering a new connection attempt
2663
+ // The useEditorWebSocket hook will check availability again
2664
+ const socket = globalThis.editorSocket;
2665
+ if (socket) {
2666
+ socket.close();
2667
+ delete globalThis.editorSocket;
2668
+ }
2669
+ // The hook will automatically attempt to reconnect
2670
+ }, []);
2239
2671
  const { socketRef: socketInstanceRef } = useEditorWebSocket({
2240
2672
  sessionId,
2241
2673
  onMessage: messageHandler,
2242
2674
  onOpen: async () => {
2675
+ // Clear concurrent user limit error on successful connection
2676
+ setConcurrentUserLimitError(null);
2677
+ // Startup WebSocket probe may have failed with a blocking error while seats were full;
2678
+ // re-run status checks quietly so "1 error" does not stick after a successful connect.
2679
+ void startupChecks.recheckQuiet();
2243
2680
  // Increment socket connection version to trigger re-subscriptions
2244
2681
  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
2682
  // Fetch any running operations on (re)connect for auto-resume
2262
2683
  // This ensures the UI shows operations that are still executing
2263
2684
  try {
@@ -2273,24 +2694,62 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2273
2694
  }
2274
2695
  },
2275
2696
  onError: (error) => console.error("WebSocket error:", error),
2697
+ onConcurrentUserLimit: (error) => {
2698
+ setConcurrentUserLimitError(error);
2699
+ },
2700
+ onSessionRevoked: () => promptSessionReconnect("session-revoked"),
2276
2701
  connectSocket,
2277
2702
  requestQuota,
2278
2703
  sendClientInfo,
2704
+ setSocketDiagnostics,
2279
2705
  });
2706
+ useEffect(() => {
2707
+ const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
2708
+ if (hasMySession &&
2709
+ socketInstanceRef.current?.readyState === WebSocket.OPEN) {
2710
+ toast.dismiss("session-revoked");
2711
+ }
2712
+ }, [activeSessions, sessionId, socketConnectionVersion, socketInstanceRef]);
2713
+ useEffect(() => {
2714
+ const handleRevoked = (event) => {
2715
+ const customEvent = event;
2716
+ promptSessionReconnect(customEvent?.detail?.reason);
2717
+ };
2718
+ window.addEventListener("parhelia:session-revoked", handleRevoked);
2719
+ return () => {
2720
+ window.removeEventListener("parhelia:session-revoked", handleRevoked);
2721
+ };
2722
+ }, [promptSessionReconnect]);
2723
+ // Show a single "session expired" toast with a Login action when any
2724
+ // service call (or the keepalive check) detects that the user is no
2725
+ // longer authenticated. Using a stable toast id coalesces the toast so a
2726
+ // burst of failing calls does not spam the UI with duplicate toasts.
2727
+ useEffect(() => {
2728
+ if (typeof window === "undefined")
2729
+ return;
2730
+ const handleExpired = () => {
2731
+ toast.error("Your session has expired", {
2732
+ id: "session-expired",
2733
+ description: "Please login again to continue editing.",
2734
+ action: {
2735
+ label: "Login",
2736
+ onClick: () => {
2737
+ window.location.href = "/sitecore/login";
2738
+ },
2739
+ },
2740
+ duration: Infinity,
2741
+ });
2742
+ };
2743
+ window.addEventListener("parhelia:session-expired", handleExpired);
2744
+ return () => {
2745
+ window.removeEventListener("parhelia:session-expired", handleExpired);
2746
+ };
2747
+ }, []);
2280
2748
  const sendSocketMessage = useCallback((message) => {
2281
2749
  if (socketInstanceRef.current &&
2282
2750
  socketInstanceRef.current.readyState === WebSocket.OPEN) {
2283
2751
  socketInstanceRef.current.send(JSON.stringify(message));
2284
2752
  }
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
2753
  }, [socketInstanceRef]);
2295
2754
  // URL update helper - defined early so it can be used by workspace/sidebar functions
2296
2755
  const updateUrl = useCallback((params) => {
@@ -2309,7 +2768,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2309
2768
  ? `${browserPathname}?${queryString}`
2310
2769
  : browserPathname;
2311
2770
  if (typeof window !== "undefined") {
2312
- window.history.pushState(null, "", newUrl);
2771
+ window.history.pushState(getCurrentHistoryState(), "", newUrl);
2772
+ lastUrlRef.current = newUrl;
2313
2773
  }
2314
2774
  else {
2315
2775
  router.push(newUrl, { scroll: false });
@@ -2334,8 +2794,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2334
2794
  if (!options?.skipNavigationHistory) {
2335
2795
  addNavigationEntry(targetWorkspaceId, item);
2336
2796
  }
2337
- // Update URL
2338
- updateUrl({ workspace: targetWorkspaceId });
2797
+ // Mark that we're pushing from switchWorkspace so the URL sync effect
2798
+ // will replaceState instead of pushing a second history entry.
2799
+ switchWorkspacePushedRef.current = true;
2800
+ updateUrl({
2801
+ workspace: targetWorkspaceId,
2802
+ ...options?.urlParams,
2803
+ });
2339
2804
  if (typeof document.startViewTransition === "function") {
2340
2805
  document.startViewTransition(() => {
2341
2806
  flushSync(() => {
@@ -2355,14 +2820,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2355
2820
  updateUrl,
2356
2821
  handleSetShowAgentsPanel,
2357
2822
  ]);
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
2823
  // Helper: get all sidebar IDs that should be preserved (locked sidebars + their stack mates)
2367
2824
  const getPreservedSidebarIds = useCallback(() => {
2368
2825
  const lockedSet = new Set(lockedSidebarsRef.current);
@@ -2400,8 +2857,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2400
2857
  setOpenSidebars(newSidebars);
2401
2858
  setLockedSidebars((locked) => locked.filter((id) => id !== sidebarId));
2402
2859
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2860
+ if (sidebarId === "agents-panel") {
2861
+ setUserPreferences({ showAgentsPanel: false });
2862
+ }
2403
2863
  startTransition(() => {
2404
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2864
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2405
2865
  });
2406
2866
  return;
2407
2867
  }
@@ -2414,34 +2874,69 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2414
2874
  ];
2415
2875
  openSidebarsRef.current = newSidebars;
2416
2876
  setOpenSidebars(newSidebars);
2877
+ ensureSidebarPinned(sidebarId);
2417
2878
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2879
+ if (sidebarId === "agents-panel") {
2880
+ setUserPreferences({ showAgentsPanel: true });
2881
+ }
2882
+ // On mobile, close the editor form panel when opening a sidebar
2883
+ if (isMobile) {
2884
+ setMobileEditorPanelOpenRaw(false);
2885
+ }
2418
2886
  startTransition(() => {
2419
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2887
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2420
2888
  });
2421
- }, [updateUrl, getPreservedSidebarIds, normalizeSidebarStacks]);
2889
+ }, [
2890
+ updateUrl,
2891
+ getPreservedSidebarIds,
2892
+ normalizeSidebarStacks,
2893
+ ensureSidebarPinned,
2894
+ setUserPreferences,
2895
+ isMobile,
2896
+ ]);
2422
2897
  // Ensure a sidebar is open (without toggling it closed if already open)
2423
- const openSidebar = useCallback((sidebarId) => {
2898
+ const openSidebar = useCallback((sidebarId, options) => {
2424
2899
  const currentOpenSidebars = openSidebarsRef.current;
2425
2900
  if (currentOpenSidebars.includes(sidebarId)) {
2426
2901
  // Already open, nothing to do
2427
2902
  return;
2428
2903
  }
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
- ];
2904
+ const preservedSet = options?.preserveOpenSidebars
2905
+ ? undefined
2906
+ : getPreservedSidebarIds();
2907
+ const newSidebars = options?.preserveOpenSidebars
2908
+ ? [...currentOpenSidebars, sidebarId]
2909
+ : [
2910
+ ...currentOpenSidebars.filter((id) => preservedSet?.has(id)),
2911
+ sidebarId,
2912
+ ];
2435
2913
  openSidebarsRef.current = newSidebars;
2436
2914
  setOpenSidebars(newSidebars);
2915
+ ensureSidebarPinned(sidebarId);
2437
2916
  setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
2917
+ if (sidebarId === "agents-panel") {
2918
+ setUserPreferences({ showAgentsPanel: true });
2919
+ }
2920
+ // On mobile, close the editor form panel when opening a sidebar
2921
+ if (isMobile) {
2922
+ setMobileEditorPanelOpenRaw(false);
2923
+ }
2438
2924
  startTransition(() => {
2439
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2925
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2440
2926
  });
2441
- }, [updateUrl, getPreservedSidebarIds, normalizeSidebarStacks]);
2927
+ }, [
2928
+ updateUrl,
2929
+ getPreservedSidebarIds,
2930
+ normalizeSidebarStacks,
2931
+ ensureSidebarPinned,
2932
+ setUserPreferences,
2933
+ isMobile,
2934
+ ]);
2442
2935
  // Toggle lock state for a sidebar stack (keeps it visible when selecting another)
2443
2936
  // When toggling any sidebar, we toggle the entire stack it belongs to
2444
2937
  const toggleSidebarLock = useCallback((sidebarId) => {
2938
+ if (isMobile)
2939
+ return;
2445
2940
  const currentStacks = sidebarStacksRef.current;
2446
2941
  const stackContainingSidebar = currentStacks.find((stack) => stack.includes(sidebarId));
2447
2942
  const sidebarIdsToToggle = stackContainingSidebar || [sidebarId];
@@ -2459,7 +2954,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2459
2954
  ];
2460
2955
  }
2461
2956
  });
2462
- }, []);
2957
+ }, [isMobile]);
2463
2958
  const stackSidebar = useCallback((sidebarId, targetSidebarId, position = "after") => {
2464
2959
  if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId)
2465
2960
  return;
@@ -2491,6 +2986,38 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2491
2986
  return normalizeSidebarStacks(openIds, next);
2492
2987
  });
2493
2988
  }, [normalizeSidebarStacks]);
2989
+ const moveSidebarToColumn = useCallback((sidebarId, targetSidebarId, position = "after") => {
2990
+ if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId) {
2991
+ return;
2992
+ }
2993
+ const currentOpen = openSidebarsRef.current;
2994
+ const baseOpen = currentOpen.filter((id) => id !== sidebarId);
2995
+ const targetIndex = baseOpen.indexOf(targetSidebarId);
2996
+ if (targetIndex === -1) {
2997
+ return;
2998
+ }
2999
+ const insertIndex = position === "before" ? targetIndex : targetIndex + 1;
3000
+ const nextOpen = [...baseOpen];
3001
+ nextOpen.splice(insertIndex, 0, sidebarId);
3002
+ openSidebarsRef.current = nextOpen;
3003
+ setOpenSidebars(nextOpen);
3004
+ startTransition(() => {
3005
+ updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
3006
+ });
3007
+ setSidebarStacks((prev) => {
3008
+ const normalized = normalizeSidebarStacks(nextOpen, prev);
3009
+ const next = normalized
3010
+ .map((stack) => stack.filter((id) => id !== sidebarId))
3011
+ .filter((stack) => stack.length > 0);
3012
+ const targetStackIndex = next.findIndex((stack) => stack.includes(targetSidebarId));
3013
+ if (targetStackIndex === -1) {
3014
+ next.push([sidebarId]);
3015
+ return normalizeSidebarStacks(nextOpen, next);
3016
+ }
3017
+ next.splice(position === "before" ? targetStackIndex : targetStackIndex + 1, 0, [sidebarId]);
3018
+ return normalizeSidebarStacks(nextOpen, next);
3019
+ });
3020
+ }, [normalizeSidebarStacks, updateUrl]);
2494
3021
  const unstackSidebar = useCallback((sidebarId) => {
2495
3022
  setSidebarStacks((prev) => {
2496
3023
  const openIds = openSidebarsRef.current;
@@ -2536,8 +3063,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2536
3063
  // Get resolved sidebar (with panels materialized)
2537
3064
  // Note: This is defined as a function that will be called later when editContext is available
2538
3065
  const sidebars = configuration.editor.sidebars ?? [];
3066
+ const taskboardSidebarIds = new Set([
3067
+ "taskboard-project-list",
3068
+ "taskboard-my-tasks",
3069
+ ]);
3070
+ const getSidebarsForWorkspace = useCallback((targetWorkspaceId) => {
3071
+ const isTaskboardWorkspace = targetWorkspaceId === "taskboard";
3072
+ const workspaceAllowedSidebarIds = userInfo.workspaces?.find((w) => w.id === targetWorkspaceId)
3073
+ ?.sidebars ?? [];
3074
+ return sidebars.filter((s) => {
3075
+ const isTaskboardSidebar = taskboardSidebarIds.has(s.id);
3076
+ if (isTaskboardWorkspace && !isTaskboardSidebar)
3077
+ return false;
3078
+ if (!isTaskboardWorkspace && isTaskboardSidebar)
3079
+ return false;
3080
+ // Always show agents-panel regardless of workspace settings.
3081
+ if (s.id === "agents-panel") {
3082
+ return true;
3083
+ }
3084
+ // If no workspace settings or no sidebars defined for current workspace, show all.
3085
+ if (workspaceAllowedSidebarIds.length === 0) {
3086
+ return true;
3087
+ }
3088
+ // Only show sidebars that are in the allowed list for the current workspace.
3089
+ return workspaceAllowedSidebarIds.includes(s.id);
3090
+ });
3091
+ }, [sidebars, userInfo.workspaces]);
2539
3092
  const getResolvedSidebar = useCallback((sidebarId) => {
2540
- const sidebar = sidebars.find((s) => s.id === sidebarId);
3093
+ const sidebar = getSidebarsForWorkspace(workspaceId).find((s) => s.id === sidebarId);
2541
3094
  if (!sidebar)
2542
3095
  return undefined;
2543
3096
  // Resolve panel factories using editContextRef to avoid circular dependency
@@ -2551,7 +3104,44 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2551
3104
  ...sidebar,
2552
3105
  panels: resolvedPanels,
2553
3106
  };
2554
- }, [sidebars]);
3107
+ }, [getSidebarsForWorkspace, workspaceId]);
3108
+ // Track the last workspaceId this effect saw so we only auto-apply
3109
+ // `currentWorkspace.defaultSidebars` on an actual workspace change. On initial mount
3110
+ // the user's explicit empty state (sidebar=none in URL) must not be overwritten.
3111
+ const prevWorkspaceIdForDefaultsRef = useRef(null);
3112
+ useEffect(() => {
3113
+ if (!currentWorkspace.supportsSidebars) {
3114
+ return;
3115
+ }
3116
+ const prev = prevWorkspaceIdForDefaultsRef.current;
3117
+ prevWorkspaceIdForDefaultsRef.current = workspaceId;
3118
+ const isWorkspaceChange = prev !== null && prev !== workspaceId;
3119
+ const allowedIds = new Set(getSidebarsForWorkspace(workspaceId).map((sidebar) => sidebar.id));
3120
+ const currentOpen = openSidebarsRef.current;
3121
+ let nextOpen = currentOpen.filter((id) => allowedIds.has(id));
3122
+ if (isWorkspaceChange &&
3123
+ nextOpen.length === 0 &&
3124
+ currentWorkspace.defaultSidebars?.length) {
3125
+ nextOpen = currentWorkspace.defaultSidebars.filter((id) => allowedIds.has(id));
3126
+ }
3127
+ const unchanged = nextOpen.length === currentOpen.length &&
3128
+ nextOpen.every((id, index) => id === currentOpen[index]);
3129
+ if (unchanged) {
3130
+ return;
3131
+ }
3132
+ openSidebarsRef.current = nextOpen;
3133
+ setOpenSidebars(nextOpen);
3134
+ setSidebarStacks((prev) => normalizeSidebarStacks(nextOpen, prev));
3135
+ startTransition(() => {
3136
+ updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
3137
+ });
3138
+ }, [
3139
+ currentWorkspace,
3140
+ getSidebarsForWorkspace,
3141
+ normalizeSidebarStacks,
3142
+ updateUrl,
3143
+ workspaceId,
3144
+ ]);
2555
3145
  // Listen for switch-workspace and open-sidebar commands from agents via websocket
2556
3146
  useEffect(() => {
2557
3147
  const handleAgentMessage = (message) => {
@@ -2765,9 +3355,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2765
3355
  }
2766
3356
  return true;
2767
3357
  }, [operations, ignoreBlur, sessionId]);
3358
+ const quickSwitcherEntries = useMemo(() => {
3359
+ const entries = [];
3360
+ const seen = new Set();
3361
+ const pushEntry = (entry) => {
3362
+ const key = entry.item
3363
+ ? `${entry.workspaceId}:${entry.item.id}:${entry.item.language}:${entry.item.version}`
3364
+ : `${entry.workspaceId}:no-item`;
3365
+ if (seen.has(key))
3366
+ return;
3367
+ seen.add(key);
3368
+ entries.push(entry);
3369
+ };
3370
+ for (const entry of navigationHistory) {
3371
+ pushEntry(entry);
3372
+ }
3373
+ for (const entry of browseHistory) {
3374
+ if (!entry.id || !entry.language)
3375
+ continue;
3376
+ pushEntry({
3377
+ workspaceId,
3378
+ item: {
3379
+ id: entry.id,
3380
+ language: entry.language,
3381
+ version: entry.version ?? 0,
3382
+ },
3383
+ timestamp: entry.visitedAt
3384
+ ? new Date(entry.visitedAt).getTime()
3385
+ : Date.now(),
3386
+ displayName: entry.name || workspaceId,
3387
+ itemName: entry.name,
3388
+ itemPath: entry.path,
3389
+ itemIcon: entry.icon,
3390
+ });
3391
+ }
3392
+ return entries.slice(0, 25);
3393
+ }, [navigationHistory, browseHistory, workspaceId]);
2768
3394
  // Quick switcher handlers
2769
3395
  const showQuickSwitcher = useCallback((show) => {
2770
- if (show && navigationHistory.length > 1) {
3396
+ if (show && quickSwitcherEntries.length > 1) {
2771
3397
  setQuickSwitcherVisible(true);
2772
3398
  // Start with index 1 (second entry - previous entry) for quick switching
2773
3399
  setQuickSwitcherSelectedIndex(1);
@@ -2775,11 +3401,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2775
3401
  else {
2776
3402
  setQuickSwitcherVisible(false);
2777
3403
  }
2778
- }, [navigationHistory]);
3404
+ }, [quickSwitcherEntries]);
2779
3405
  const cycleQuickSwitcher = useCallback((direction) => {
2780
3406
  if (!quickSwitcherVisible)
2781
3407
  return;
2782
- const maxItems = Math.min(5, navigationHistory.length);
3408
+ const maxItems = Math.min(5, quickSwitcherEntries.length);
2783
3409
  setQuickSwitcherSelectedIndex((current) => {
2784
3410
  let newIndex = current;
2785
3411
  // Determine grid layout (responsive columns)
@@ -2824,9 +3450,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2824
3450
  }
2825
3451
  return newIndex;
2826
3452
  });
2827
- }, [quickSwitcherVisible, navigationHistory]);
3453
+ }, [quickSwitcherVisible, quickSwitcherEntries]);
2828
3454
  const handleQuickSwitcherSelect = useCallback((index) => {
2829
- const selectedEntry = navigationHistory[index];
3455
+ const selectedEntry = quickSwitcherEntries[index];
2830
3456
  if (selectedEntry) {
2831
3457
  // Determine target workspace: entries with items should go to "editor" workspace
2832
3458
  // (fixes issue where browse history entries initialized with wrong workspaceId)
@@ -2878,23 +3504,46 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2878
3504
  }
2879
3505
  setQuickSwitcherVisible(false);
2880
3506
  }, [
2881
- navigationHistory,
3507
+ quickSwitcherEntries,
2882
3508
  loadItem,
2883
3509
  switchWorkspace,
2884
3510
  workspaceId,
2885
3511
  item,
2886
3512
  setNavigationHistory,
2887
3513
  ]);
3514
+ useEffect(() => {
3515
+ if (typeof window === "undefined" ||
3516
+ process.env.PARHELIA_DEV_MODE !== "true") {
3517
+ return;
3518
+ }
3519
+ window.__parheliaQuickSwitcherTestApi = {
3520
+ open: () => showQuickSwitcher(true),
3521
+ cycle: (direction = "next") => cycleQuickSwitcher(direction),
3522
+ closeWithoutSelection: () => setQuickSwitcherVisible(false),
3523
+ selectCurrent: () => handleQuickSwitcherSelect(quickSwitcherSelectedIndex),
3524
+ getState: () => ({
3525
+ visible: quickSwitcherVisible,
3526
+ selectedIndex: quickSwitcherSelectedIndex,
3527
+ entryCount: quickSwitcherEntries.length,
3528
+ entries: quickSwitcherEntries.map((entry) => ({
3529
+ workspaceId: entry.workspaceId,
3530
+ itemId: entry.item?.id,
3531
+ displayName: entry.displayName,
3532
+ })),
3533
+ }),
3534
+ };
3535
+ return () => {
3536
+ delete window.__parheliaQuickSwitcherTestApi;
3537
+ };
3538
+ }, [
3539
+ showQuickSwitcher,
3540
+ cycleQuickSwitcher,
3541
+ handleQuickSwitcherSelect,
3542
+ quickSwitcherSelectedIndex,
3543
+ ]);
2888
3544
  const { handleKeyDown } = useKeyboardNavigation({
2889
3545
  editContextRef,
2890
- operations,
2891
- pageViewContext: activePageViewContext,
2892
- configuration,
2893
- item,
2894
- browseHistory,
2895
- loadItem,
2896
- showInfoToast,
2897
- showErrorToast,
3546
+ keyboardCommands: activeKeyboardCommands,
2898
3547
  executeCommand,
2899
3548
  showQuickSwitcher,
2900
3549
  cycleQuickSwitcher,
@@ -3014,10 +3663,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3014
3663
  }
3015
3664
  return null;
3016
3665
  };
3666
+ let modifierWasPressedOnMouseDown = false;
3667
+ const handleMouseDown = (event) => {
3668
+ modifierWasPressedOnMouseDown = event.ctrlKey || event.metaKey;
3669
+ };
3017
3670
  const handleCtrlClick = async (event) => {
3018
- // Only proceed if Ctrl (or Cmd on Mac) is pressed
3671
+ // Only proceed if Ctrl/Cmd was already pressed when the mouse interaction started.
3672
+ // This avoids accidental navigation when users press Ctrl after selecting text to copy.
3673
+ if (!modifierWasPressedOnMouseDown)
3674
+ return;
3675
+ // Also require the modifier to still be held for the final click event.
3019
3676
  if (!event.ctrlKey && !event.metaKey)
3020
3677
  return;
3678
+ modifierWasPressedOnMouseDown = false;
3021
3679
  const target = event.target;
3022
3680
  const text = getTextFromElement(target);
3023
3681
  if (text && isGuid(text)) {
@@ -3032,8 +3690,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3032
3690
  skipViewChange: true,
3033
3691
  });
3034
3692
  if (item) {
3035
- // Switch to the editor view
3036
- switchView("editor", {
3693
+ switchWorkspace("editor", {
3037
3694
  skipNavigationHistory: true,
3038
3695
  });
3039
3696
  showInfoToast({
@@ -3059,12 +3716,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3059
3716
  }
3060
3717
  };
3061
3718
  if (typeof document !== "undefined") {
3719
+ document.addEventListener("mousedown", handleMouseDown, true);
3062
3720
  document.addEventListener("click", handleCtrlClick, true);
3063
3721
  return () => {
3722
+ document.removeEventListener("mousedown", handleMouseDown, true);
3064
3723
  document.removeEventListener("click", handleCtrlClick, true);
3065
3724
  };
3066
3725
  }
3067
- }, [loadItem, switchView, showInfoToast, showErrorToast]);
3726
+ }, [loadItem, switchWorkspace, showInfoToast, showErrorToast]);
3068
3727
  useEffect(() => {
3069
3728
  const handleGlobalBlur = () => {
3070
3729
  operations.onFieldBlur?.();
@@ -3102,21 +3761,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3102
3761
  // Otherwise, only show workspaces that are in the settings
3103
3762
  return workspaceIdsFromSettings.includes(w.id) && !w.visible;
3104
3763
  });
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
3764
  // Handle initial mode setup from URL (only on initial load)
3121
3765
  useEffect(() => {
3122
3766
  if (!isInitialLoad)
@@ -3161,7 +3805,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3161
3805
  // This is especially important when called from the tour, where the current workspace
3162
3806
  // might be the editor and URL-only navigation would get "corrected" back.
3163
3807
  try {
3164
- switchView("home", {
3808
+ switchWorkspace("home", {
3165
3809
  skipConfirmation: true,
3166
3810
  skipNavigationHistory: true,
3167
3811
  });
@@ -3208,7 +3852,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3208
3852
  const current = new URLSearchParams(searchParams.toString());
3209
3853
  current.delete("version");
3210
3854
  current.delete("itemid");
3211
- current.delete("view"); // Remove legacy param
3212
3855
  current.delete("workspace"); // Clear workspace
3213
3856
  current.set("create", "1");
3214
3857
  const newUrl = `${pathname}?${current.toString()}`;
@@ -3488,6 +4131,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3488
4131
  setShowOnlyMyChanges,
3489
4132
  filterByCurrentLanguage,
3490
4133
  setFilterByCurrentLanguage,
4134
+ historySearchQuery,
4135
+ setHistorySearchQuery,
3491
4136
  refreshHistory,
3492
4137
  isRefreshing,
3493
4138
  activeSessions,
@@ -3525,19 +4170,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3525
4170
  workspaceId,
3526
4171
  previousWorkspaceId,
3527
4172
  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
- }),
4173
+ // Sidebar state
4174
+ availableSidebars: getSidebarsForWorkspace(workspaceId),
3541
4175
  openSidebars,
3542
4176
  pinnedSidebars,
3543
4177
  lockedSidebars,
@@ -3547,17 +4181,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3547
4181
  toggleSidebarPin,
3548
4182
  toggleSidebarLock,
3549
4183
  stackSidebar,
4184
+ moveSidebarToColumn,
3550
4185
  unstackSidebar,
3551
4186
  reorderSidebarInStack,
3552
4187
  reorderPinnedSidebars,
3553
4188
  reorderOpenSidebars,
3554
4189
  getResolvedSidebar,
3555
- // Legacy compatibility (deprecated)
3556
- viewName,
3557
- previousViewName,
3558
- switchView,
3559
- view: currentView,
3560
- visibleViews,
3561
4190
  compareMode,
3562
4191
  setCompareMode,
3563
4192
  fullscreen,
@@ -3597,6 +4226,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3597
4226
  addSocketMessageListener,
3598
4227
  sendSocketMessage,
3599
4228
  socketConnectionVersion,
4229
+ socketDiagnostics,
3600
4230
  currentItemDescriptor,
3601
4231
  editorSlots,
3602
4232
  activeSlotId,
@@ -3609,6 +4239,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3609
4239
  setActiveSlot,
3610
4240
  revision,
3611
4241
  notifyPageModelReady,
4242
+ pageModelReadyToken,
3612
4243
  selectedComment,
3613
4244
  setSelectedComment,
3614
4245
  comments,
@@ -3687,8 +4318,18 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3687
4318
  setEnableCompletions,
3688
4319
  showComponentNavigator,
3689
4320
  setShowComponentNavigator: handleSetShowComponentNavigator,
4321
+ isComponentNavigatorOpenForSlot,
4322
+ setComponentNavigatorOpenForSlot,
3690
4323
  showAgentsPanel,
3691
4324
  setShowAgentsPanel: handleSetShowAgentsPanel,
4325
+ editorFormHidden,
4326
+ setEditorFormHidden: handleSetEditorFormHidden,
4327
+ editorFormHintSeen,
4328
+ markEditorFormHintSeen,
4329
+ editorFormHintVisible,
4330
+ showEditorFormHint,
4331
+ dismissEditorFormHint,
4332
+ editorFormToggleButtonRef,
3692
4333
  showMinimap,
3693
4334
  setShowMinimap: handleSetShowMinimap,
3694
4335
  showHelpTerminal,
@@ -3698,8 +4339,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3698
4339
  helpTerminalProfileName,
3699
4340
  helpTerminalActiveTab,
3700
4341
  setHelpTerminalActiveTab,
4342
+ selectedHelpSectionId,
4343
+ setSelectedHelpSectionId,
3701
4344
  showAgentsWorkspaceEditor,
3702
4345
  setShowAgentsWorkspaceEditor: handleSetShowAgentsWorkspaceEditor,
4346
+ selectedAgentsWorkspaceAgentId,
4347
+ setSelectedAgentsWorkspaceAgentId,
3703
4348
  activeEditorTab,
3704
4349
  setActiveEditorTab,
3705
4350
  showLayoutComponents,
@@ -3708,6 +4353,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3708
4353
  isQuotaExceeded: isQuotaExceeded(),
3709
4354
  getQuotaWarningMessage,
3710
4355
  isMobile,
4356
+ mobileEditorPanelOpen,
4357
+ setMobileEditorPanelOpen: handleSetMobileEditorPanelOpen,
3711
4358
  openDialog,
3712
4359
  webSocketMessages,
3713
4360
  clearWebSocketMessages: () => setWebSocketMessages([]),
@@ -3746,7 +4393,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3746
4393
  configuration,
3747
4394
  updateUrl,
3748
4395
  workspaceId,
3749
- switchView,
3750
4396
  pathname,
3751
4397
  router,
3752
4398
  item,
@@ -3776,7 +4422,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3776
4422
  currentWorkspace,
3777
4423
  previousWorkspaceId,
3778
4424
  switchWorkspace,
3779
- allowedSidebarIds,
3780
4425
  openSidebars,
3781
4426
  pinnedSidebars,
3782
4427
  lockedSidebars,
@@ -3788,9 +4433,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3788
4433
  reorderOpenSidebars,
3789
4434
  getResolvedSidebar,
3790
4435
  viewName,
3791
- previousViewName,
3792
- currentView,
3793
- visibleViews,
3794
4436
  compareMode,
3795
4437
  // Important: in multi-slot mode the active PageViewContext can change
3796
4438
  // without the base `pageViewContext` identity changing (e.g. switching slots).
@@ -3815,6 +4457,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3815
4457
  currentItemDescriptor,
3816
4458
  revision,
3817
4459
  notifyPageModelReady,
4460
+ pageModelReadyToken,
3818
4461
  selectedComment,
3819
4462
  comments,
3820
4463
  availableCommentTags,
@@ -3833,6 +4476,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3833
4476
  quickSwitcherSelectedIndex,
3834
4477
  handleQuickSwitcherSelect,
3835
4478
  webSocketMessages,
4479
+ socketDiagnostics,
3836
4480
  factoriesRef,
3837
4481
  user,
3838
4482
  statusMessage,
@@ -3841,10 +4485,22 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3841
4485
  isQuotaExceeded,
3842
4486
  getQuotaWarningMessage,
3843
4487
  isMobile,
4488
+ mobileEditorPanelOpen,
4489
+ handleSetMobileEditorPanelOpen,
3844
4490
  showComponentNavigator,
4491
+ isComponentNavigatorOpenForSlot,
4492
+ setComponentNavigatorOpenForSlot,
3845
4493
  handleSetShowComponentNavigator,
3846
4494
  showAgentsPanel,
3847
4495
  handleSetShowAgentsPanel,
4496
+ editorFormHidden,
4497
+ handleSetEditorFormHidden,
4498
+ editorFormHintSeen,
4499
+ markEditorFormHintSeen,
4500
+ editorFormHintVisible,
4501
+ showEditorFormHint,
4502
+ dismissEditorFormHint,
4503
+ editorFormToggleButtonRef,
3848
4504
  showMinimap,
3849
4505
  handleSetShowMinimap,
3850
4506
  showHelpTerminal,
@@ -3853,7 +4509,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3853
4509
  helpTerminalProfileName,
3854
4510
  helpTerminalActiveTab,
3855
4511
  setHelpTerminalActiveTab,
4512
+ selectedHelpSectionId,
3856
4513
  showAgentsWorkspaceEditor,
4514
+ selectedAgentsWorkspaceAgentId,
3857
4515
  activeEditorTab,
3858
4516
  showLayoutComponents,
3859
4517
  openDialog,
@@ -4098,18 +4756,40 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4098
4756
  // prevDependencies.current = currentDependencies;
4099
4757
  // editContextRef.current = editContext;
4100
4758
  // }, [editContext]);
4759
+ // Auto-open the mobile editor panel for new selection/intent changes, but
4760
+ // keep a manual close sticky for the exact same context.
4761
+ useEffect(() => {
4762
+ if (!isMobile || workspaceId !== "editor")
4763
+ return;
4764
+ if (activeEditorTab) {
4765
+ handleSetMobileEditorPanelOpen(true);
4766
+ return;
4767
+ }
4768
+ if (!(selection.length > 0 || insertMode))
4769
+ return;
4770
+ if (dismissedMobilePanelToken === mobilePanelDismissToken)
4771
+ return;
4772
+ handleSetMobileEditorPanelOpen(true);
4773
+ }, [
4774
+ activeEditorTab,
4775
+ dismissedMobilePanelToken,
4776
+ handleSetMobileEditorPanelOpen,
4777
+ insertMode,
4778
+ isMobile,
4779
+ mobilePanelDismissToken,
4780
+ selection,
4781
+ workspaceId,
4782
+ ]);
4101
4783
  useEffect(() => {
4102
4784
  fieldsEditContext.clearModifiedFields();
4103
4785
  }, [currentItemDescriptor]);
4104
- if (!currentView)
4786
+ if (!currentWorkspace)
4105
4787
  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: () => {
4788
+ 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
4789
  setTimeout(() => {
4108
4790
  setShowFullscreenHint(false);
4109
4791
  }, 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: () => {
4792
+ }, "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
4793
  setIsTourActive(false);
4114
4794
  // Remove tour state from URL
4115
4795
  // Use history.replaceState instead of router.replace to avoid triggering React navigation
@@ -4119,8 +4799,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4119
4799
  const newUrl = queryString
4120
4800
  ? `${window.location.pathname}?${queryString}`
4121
4801
  : 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 })] }) }) }) }));
4802
+ window.history.replaceState(getCurrentHistoryState(), "", newUrl);
4803
+ }, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(GuidanceOverlay, {}) }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(AgentDialogHandler, {}) })] }));
4804
+ 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" ||
4805
+ currentWorkspace.id === "taskboard") &&
4806
+ 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) => {
4807
+ if (!open) {
4808
+ setConcurrentUserLimitError(null);
4809
+ }
4810
+ }, 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(EditorFormHintPopover, {}), _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
4811
  }
4126
4812
  //# sourceMappingURL=EditorShell.js.map