@alpaca-editor/core 1.0.4172 → 1.0.4174

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 (170) hide show
  1. package/dist/agents-view/AgentsView.d.ts +5 -0
  2. package/dist/agents-view/AgentsView.js +213 -0
  3. package/dist/agents-view/AgentsView.js.map +1 -0
  4. package/dist/components/ui/context-menu.js +4 -4
  5. package/dist/components/ui/context-menu.js.map +1 -1
  6. package/dist/config/config.js +56 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/editor/ConfirmationDialog.js +2 -1
  9. package/dist/editor/ConfirmationDialog.js.map +1 -1
  10. package/dist/editor/ContentTree.d.ts +2 -1
  11. package/dist/editor/ContentTree.js +18 -3
  12. package/dist/editor/ContentTree.js.map +1 -1
  13. package/dist/editor/ContextMenu.js +1 -1
  14. package/dist/editor/ContextMenu.js.map +1 -1
  15. package/dist/editor/FieldList.js +7 -3
  16. package/dist/editor/FieldList.js.map +1 -1
  17. package/dist/editor/FieldListField.d.ts +3 -2
  18. package/dist/editor/FieldListField.js +4 -4
  19. package/dist/editor/FieldListField.js.map +1 -1
  20. package/dist/editor/FieldListFieldWithFallbacks.d.ts +2 -1
  21. package/dist/editor/FieldListFieldWithFallbacks.js +5 -2
  22. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  23. package/dist/editor/MainLayout.js +1 -1
  24. package/dist/editor/QuickItemSwitcher.d.ts +9 -0
  25. package/dist/editor/QuickItemSwitcher.js +74 -0
  26. package/dist/editor/QuickItemSwitcher.js.map +1 -0
  27. package/dist/editor/ai/AgentCostDisplay.js +7 -11
  28. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  29. package/dist/editor/ai/AgentProfilesOverview.d.ts +9 -0
  30. package/dist/editor/ai/AgentProfilesOverview.js +16 -0
  31. package/dist/editor/ai/AgentProfilesOverview.js.map +1 -0
  32. package/dist/editor/ai/AgentTerminal.js +285 -748
  33. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  34. package/dist/editor/ai/Agents.js +112 -54
  35. package/dist/editor/ai/Agents.js.map +1 -1
  36. package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
  37. package/dist/editor/ai/AiResponseMessage.js +4 -2
  38. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  39. package/dist/editor/ai/ContextInfoBar.js +17 -17
  40. package/dist/editor/ai/useAgentStatus.js +7 -0
  41. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  42. package/dist/editor/client/EditorShell.js +230 -4
  43. package/dist/editor/client/EditorShell.js.map +1 -1
  44. package/dist/editor/client/hooks/useSocketMessageHandler.js +0 -12
  45. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  46. package/dist/editor/client/ui/EditorChrome.js +1 -1
  47. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  48. package/dist/editor/commands/itemCommands.js +1 -0
  49. package/dist/editor/commands/itemCommands.js.map +1 -1
  50. package/dist/editor/control-center/parhelia-setup/Overview.d.ts +1 -0
  51. package/dist/editor/control-center/parhelia-setup/Overview.js +91 -0
  52. package/dist/editor/control-center/parhelia-setup/Overview.js.map +1 -0
  53. package/dist/editor/field-types/AttachmentEditor.js +3 -6
  54. package/dist/editor/field-types/AttachmentEditor.js.map +1 -1
  55. package/dist/editor/field-types/DropLinkEditor.js +2 -2
  56. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  57. package/dist/editor/field-types/DropListEditor.js +2 -1
  58. package/dist/editor/field-types/DropListEditor.js.map +1 -1
  59. package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -3
  60. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  61. package/dist/editor/field-types/MultiLineText.d.ts +2 -1
  62. package/dist/editor/field-types/MultiLineText.js +2 -2
  63. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  64. package/dist/editor/field-types/RawEditor.d.ts +2 -1
  65. package/dist/editor/field-types/RawEditor.js +2 -2
  66. package/dist/editor/field-types/RawEditor.js.map +1 -1
  67. package/dist/editor/field-types/SingleLineText.d.ts +2 -1
  68. package/dist/editor/field-types/SingleLineText.js +2 -2
  69. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  70. package/dist/editor/field-types/TreeListEditor.js +9 -7
  71. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  72. package/dist/editor/media-selector/MediaFolderBrowser.js +68 -7
  73. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  74. package/dist/editor/media-selector/TreeSelector.js +1 -1
  75. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  76. package/dist/editor/menubar/ActiveUsers.js +2 -2
  77. package/dist/editor/menubar/FavoritesControls.js +2 -2
  78. package/dist/editor/menubar/FavoritesControls.js.map +1 -1
  79. package/dist/editor/page-editor-chrome/FrameMenu.js +10 -1
  80. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  81. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +14 -0
  82. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  83. package/dist/editor/services/agentService.d.ts +21 -3
  84. package/dist/editor/services/agentService.js +24 -8
  85. package/dist/editor/services/agentService.js.map +1 -1
  86. package/dist/editor/services/aiService.d.ts +6 -1
  87. package/dist/editor/services/aiService.js +36 -5
  88. package/dist/editor/services/aiService.js.map +1 -1
  89. package/dist/editor/services/contentService.d.ts +1 -1
  90. package/dist/editor/services/contentService.js +4 -2
  91. package/dist/editor/services/contentService.js.map +1 -1
  92. package/dist/editor/services/editService.d.ts +5 -1
  93. package/dist/editor/services/editService.js +1 -1
  94. package/dist/editor/services/editService.js.map +1 -1
  95. package/dist/editor/services/setupService.d.ts +21 -0
  96. package/dist/editor/services/setupService.js +10 -0
  97. package/dist/editor/services/setupService.js.map +1 -0
  98. package/dist/editor/sidebar/ComponentTree.js +15 -1
  99. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  100. package/dist/editor/sidebar/SidebarView.js +1 -1
  101. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  102. package/dist/editor/ui/ItemSearch.d.ts +1 -0
  103. package/dist/editor/ui/ItemSearch.js +2 -2
  104. package/dist/editor/ui/ItemSearch.js.map +1 -1
  105. package/dist/editor/ui/PerfectTree.d.ts +5 -1
  106. package/dist/editor/ui/PerfectTree.js +308 -29
  107. package/dist/editor/ui/PerfectTree.js.map +1 -1
  108. package/dist/editor/utils/keyboardNavigation.d.ts +2 -0
  109. package/dist/editor/utils/keyboardNavigation.js +80 -2
  110. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  111. package/dist/editor/views/SingleEditView.js +6 -4
  112. package/dist/editor/views/SingleEditView.js.map +1 -1
  113. package/dist/revision.d.ts +2 -2
  114. package/dist/revision.js +2 -2
  115. package/dist/splash-screen/SplashScreen.js +78 -4
  116. package/dist/splash-screen/SplashScreen.js.map +1 -1
  117. package/dist/styles.css +157 -23
  118. package/dist/types.d.ts +13 -0
  119. package/package.json +1 -1
  120. package/src/agents-view/AgentsView.tsx +431 -0
  121. package/src/components/ui/context-menu.tsx +4 -4
  122. package/src/config/config.tsx +61 -0
  123. package/src/editor/ConfirmationDialog.tsx +42 -10
  124. package/src/editor/ContentTree.tsx +20 -1
  125. package/src/editor/ContextMenu.tsx +4 -1
  126. package/src/editor/FieldList.tsx +10 -4
  127. package/src/editor/FieldListField.tsx +7 -0
  128. package/src/editor/FieldListFieldWithFallbacks.tsx +10 -0
  129. package/src/editor/MainLayout.tsx +1 -1
  130. package/src/editor/QuickItemSwitcher.tsx +217 -0
  131. package/src/editor/ai/AgentCostDisplay.tsx +59 -60
  132. package/src/editor/ai/AgentProfilesOverview.tsx +81 -0
  133. package/src/editor/ai/AgentTerminal.tsx +321 -775
  134. package/src/editor/ai/Agents.tsx +157 -91
  135. package/src/editor/ai/AiResponseMessage.tsx +12 -1
  136. package/src/editor/ai/ContextInfoBar.tsx +17 -17
  137. package/src/editor/ai/useAgentStatus.ts +6 -0
  138. package/src/editor/client/EditorShell.tsx +288 -3
  139. package/src/editor/client/hooks/useSocketMessageHandler.ts +0 -15
  140. package/src/editor/client/ui/EditorChrome.tsx +1 -1
  141. package/src/editor/commands/itemCommands.tsx +1 -0
  142. package/src/editor/control-center/parhelia-setup/Overview.tsx +184 -0
  143. package/src/editor/field-types/AttachmentEditor.tsx +13 -50
  144. package/src/editor/field-types/DropLinkEditor.tsx +2 -2
  145. package/src/editor/field-types/DropListEditor.tsx +2 -1
  146. package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -3
  147. package/src/editor/field-types/MultiLineText.tsx +3 -0
  148. package/src/editor/field-types/RawEditor.tsx +3 -0
  149. package/src/editor/field-types/SingleLineText.tsx +3 -0
  150. package/src/editor/field-types/TreeListEditor.tsx +32 -24
  151. package/src/editor/media-selector/MediaFolderBrowser.tsx +93 -9
  152. package/src/editor/media-selector/TreeSelector.tsx +1 -0
  153. package/src/editor/menubar/ActiveUsers.tsx +2 -2
  154. package/src/editor/menubar/FavoritesControls.tsx +5 -5
  155. package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
  156. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +17 -0
  157. package/src/editor/services/agentService.ts +46 -11
  158. package/src/editor/services/aiService.ts +48 -7
  159. package/src/editor/services/contentService.ts +4 -1
  160. package/src/editor/services/editService.ts +8 -3
  161. package/src/editor/services/setupService.ts +35 -0
  162. package/src/editor/sidebar/ComponentTree.tsx +16 -1
  163. package/src/editor/sidebar/SidebarView.tsx +1 -1
  164. package/src/editor/ui/ItemSearch.tsx +3 -1
  165. package/src/editor/ui/PerfectTree.tsx +393 -42
  166. package/src/editor/utils/keyboardNavigation.ts +97 -1
  167. package/src/editor/views/SingleEditView.tsx +27 -13
  168. package/src/revision.ts +2 -2
  169. package/src/splash-screen/SplashScreen.tsx +134 -2
  170. package/src/types.ts +15 -0
@@ -81,6 +81,7 @@ import {
81
81
  EditSession,
82
82
  FieldDescriptor,
83
83
  HistoryEntry,
84
+ NavigationHistoryEntry,
84
85
  InsertOption,
85
86
  LanguageVersions,
86
87
  LinkComponentOperation,
@@ -120,6 +121,7 @@ import { Toaster } from "../../components/ui/sonner";
120
121
 
121
122
  import { Tour } from "../../tour/Tour";
122
123
  import { usePageViewContext } from "../page-viewer/pageViewContext";
124
+ import { QuickItemSwitcher } from "../QuickItemSwitcher";
123
125
 
124
126
  import {
125
127
  getComments,
@@ -373,6 +375,36 @@ export function EditorShell({
373
375
  );
374
376
  });
375
377
 
378
+ // Navigation history for browser history (view + item combinations)
379
+ const [navigationHistory, setNavigationHistory] = useState<
380
+ NavigationHistoryEntry[]
381
+ >(() => {
382
+ // Initialize from browse history with current view
383
+ if (!userInfo.browseHistory) return [];
384
+
385
+ return userInfo.browseHistory.map(
386
+ (entry): NavigationHistoryEntry => ({
387
+ viewName:
388
+ searchParams.get("view") ??
389
+ configuration.editor.views[0]?.name ??
390
+ "content-editor",
391
+ item: {
392
+ id: entry.itemId,
393
+ language: entry.itemLanguage,
394
+ version: entry.itemVersion,
395
+ },
396
+ timestamp: new Date(entry.visitedAt).getTime(),
397
+ displayName:
398
+ searchParams.get("view") ??
399
+ configuration.editor.views[0]?.name ??
400
+ "content-editor",
401
+ itemName: entry.itemName,
402
+ itemPath: entry.itemPath,
403
+ itemIcon: entry.icon,
404
+ }),
405
+ );
406
+ });
407
+
376
408
  const [centerPanelView, setCenterPanelView] = useState<ReactNode>();
377
409
  const [timings, setTimings] = useState<Timings>({});
378
410
 
@@ -388,7 +420,7 @@ export function EditorShell({
388
420
 
389
421
  const [enableCompletions, setEnableCompletions] = useState(false);
390
422
  const [showComponentNavigator, setShowComponentNavigator] = useState(
391
- userPreferences.showComponentNavigator ?? true,
423
+ userPreferences.showComponentNavigator ?? false,
392
424
  );
393
425
  const [showAgentsPanel, setShowAgentsPanel] = useState(
394
426
  userPreferences.showAgentsPanel ?? false,
@@ -409,6 +441,11 @@ export function EditorShell({
409
441
 
410
442
  const [favorites, setFavorites] = useState<any[]>([]);
411
443
 
444
+ // Quick item switcher state
445
+ const [quickSwitcherVisible, setQuickSwitcherVisible] = useState(false);
446
+ const [quickSwitcherSelectedIndex, setQuickSwitcherSelectedIndex] =
447
+ useState(0);
448
+
412
449
  // Track initial load to know when to sync state from URL vs URL from state
413
450
  const [isInitialLoad, setIsInitialLoad] = useState(true);
414
451
 
@@ -1151,6 +1188,44 @@ export function EditorShell({
1151
1188
  [browseHistory, setBrowseHistory],
1152
1189
  );
1153
1190
 
1191
+ // Add entry to navigation history (view + item combination)
1192
+ const addNavigationEntry = useCallback((view: string, item?: FullItem) => {
1193
+ const navEntry: NavigationHistoryEntry = {
1194
+ viewName: view,
1195
+ item: item
1196
+ ? {
1197
+ id: item.id,
1198
+ language: item.language,
1199
+ version: item.version,
1200
+ }
1201
+ : undefined,
1202
+ timestamp: Date.now(),
1203
+ displayName: view,
1204
+ itemName: item?.name,
1205
+ itemPath: item?.path,
1206
+ itemIcon: item?.icon,
1207
+ };
1208
+
1209
+ setNavigationHistory((history) => {
1210
+ // Remove duplicates with same view + item identity (including version),
1211
+ // and also handle entries with no item (both undefined)
1212
+ const filtered = history.filter((entry) => {
1213
+ const sameView = entry.viewName === view;
1214
+ const bothNoItem = !entry.item && !item;
1215
+ const sameItem =
1216
+ !!entry.item &&
1217
+ !!item &&
1218
+ entry.item.id === item.id &&
1219
+ entry.item.language === item.language &&
1220
+ entry.item.version === item.version;
1221
+ return !(sameView && (bothNoItem || sameItem));
1222
+ });
1223
+
1224
+ // Add new entry at the beginning and limit to 25 items
1225
+ return [navEntry, ...filtered].slice(0, 25);
1226
+ });
1227
+ }, []);
1228
+
1154
1229
  const loadItem = useCallback(
1155
1230
  async (
1156
1231
  itemToLoad: ItemDescriptor | string,
@@ -1196,6 +1271,8 @@ export function EditorShell({
1196
1271
  options?.addToBrowseHistory === undefined
1197
1272
  ) {
1198
1273
  addToBrowseHistory(item);
1274
+ // Also add to navigation history with current view
1275
+ addNavigationEntry(viewName, item);
1199
1276
  }
1200
1277
 
1201
1278
  return item;
@@ -1206,6 +1283,7 @@ export function EditorShell({
1206
1283
  viewName,
1207
1284
  loadHistory,
1208
1285
  addToBrowseHistory,
1286
+ addNavigationEntry,
1209
1287
  ],
1210
1288
  );
1211
1289
 
@@ -1416,6 +1494,9 @@ export function EditorShell({
1416
1494
  // Track previous view name before switching
1417
1495
  setPreviousViewName(editContext.viewName);
1418
1496
 
1497
+ // Add to navigation history with current item
1498
+ addNavigationEntry(viewName, contentEditorItem);
1499
+
1419
1500
  if (typeof document.startViewTransition === "function") {
1420
1501
  document.startViewTransition(() => {
1421
1502
  flushSync(() => {
@@ -1511,6 +1592,155 @@ export function EditorShell({
1511
1592
  [operations, ignoreBlur, sessionId],
1512
1593
  );
1513
1594
 
1595
+ // Quick switcher handlers
1596
+ const showQuickSwitcher = useCallback(
1597
+ (show: boolean) => {
1598
+ console.log(
1599
+ `[QuickSwitcher] showQuickSwitcher called: show=${show}, historyLength=${navigationHistory.length}`,
1600
+ );
1601
+ if (show && navigationHistory.length > 1) {
1602
+ setQuickSwitcherVisible(true);
1603
+ // Start with index 1 (second entry - previous entry) for quick switching
1604
+ setQuickSwitcherSelectedIndex(1);
1605
+ console.log("[QuickSwitcher] Switcher opened, selected index: 1");
1606
+ console.log(
1607
+ "[QuickSwitcher] Navigation history:",
1608
+ navigationHistory.map((h, i) => `[${i}] ${h.displayName}`),
1609
+ );
1610
+ } else {
1611
+ setQuickSwitcherVisible(false);
1612
+ console.log("[QuickSwitcher] Switcher closed");
1613
+ }
1614
+ },
1615
+ [navigationHistory],
1616
+ );
1617
+
1618
+ const cycleQuickSwitcher = useCallback(
1619
+ (direction: "next" | "prev" | "up" | "down") => {
1620
+ console.log(
1621
+ `[QuickSwitcher] cycleQuickSwitcher: direction=${direction}, visible=${quickSwitcherVisible}`,
1622
+ );
1623
+ if (!quickSwitcherVisible) return;
1624
+ const maxItems = Math.min(5, navigationHistory.length);
1625
+
1626
+ setQuickSwitcherSelectedIndex((current) => {
1627
+ let newIndex = current;
1628
+
1629
+ // Determine grid layout (responsive columns)
1630
+ // Matches the grid in QuickItemSwitcher: 2/3/4/5 columns
1631
+ const getColumnsForWidth = () => {
1632
+ const width = window.innerWidth;
1633
+ if (width >= 1280) return 5; // xl
1634
+ if (width >= 1024) return 4; // lg
1635
+ if (width >= 768) return 3; // md
1636
+ return 2; // default
1637
+ };
1638
+
1639
+ const columns = getColumnsForWidth();
1640
+
1641
+ switch (direction) {
1642
+ case "next":
1643
+ newIndex = (current + 1) % maxItems;
1644
+ break;
1645
+ case "prev":
1646
+ newIndex = (current - 1 + maxItems) % maxItems;
1647
+ break;
1648
+ case "down":
1649
+ newIndex = current + columns;
1650
+ if (newIndex >= maxItems) {
1651
+ // Wrap to first row
1652
+ newIndex = current % columns;
1653
+ }
1654
+ break;
1655
+ case "up":
1656
+ newIndex = current - columns;
1657
+ if (newIndex < 0) {
1658
+ // Wrap to last row
1659
+ const currentCol = current % columns;
1660
+ const rows = Math.ceil(maxItems / columns);
1661
+ newIndex = (rows - 1) * columns + currentCol;
1662
+ if (newIndex >= maxItems) {
1663
+ newIndex = maxItems - columns + currentCol;
1664
+ }
1665
+ }
1666
+ break;
1667
+ }
1668
+
1669
+ console.log(
1670
+ `[QuickSwitcher] Cycling from index ${current} to ${newIndex} (${columns} columns)`,
1671
+ );
1672
+ return newIndex;
1673
+ });
1674
+ },
1675
+ [quickSwitcherVisible, browseHistory],
1676
+ );
1677
+
1678
+ const handleQuickSwitcherSelect = useCallback(
1679
+ (index: number) => {
1680
+ console.log(`[QuickSwitcher] Selecting entry at index ${index}`);
1681
+ const selectedEntry = navigationHistory[index];
1682
+ if (selectedEntry) {
1683
+ console.log(`[QuickSwitcher] Navigating to:`, selectedEntry);
1684
+
1685
+ // First switch view
1686
+ if (selectedEntry.viewName !== viewName) {
1687
+ switchView(selectedEntry.viewName, { skipConfirmation: true });
1688
+ }
1689
+
1690
+ // Then load item if it exists and is different from current
1691
+ if (selectedEntry.item) {
1692
+ const isDifferentItem =
1693
+ !contentEditorItem ||
1694
+ contentEditorItem.id !== selectedEntry.item.id ||
1695
+ contentEditorItem.language !== selectedEntry.item.language ||
1696
+ contentEditorItem.version !== selectedEntry.item.version;
1697
+
1698
+ if (isDifferentItem) {
1699
+ console.log(
1700
+ `[QuickSwitcher] Loading different item:`,
1701
+ selectedEntry.item,
1702
+ );
1703
+ loadItem(
1704
+ {
1705
+ id: selectedEntry.item.id,
1706
+ language: selectedEntry.item.language,
1707
+ version: selectedEntry.item.version,
1708
+ },
1709
+ { addToBrowseHistory: false, skipViewChange: true },
1710
+ );
1711
+ } else {
1712
+ console.log(`[QuickSwitcher] Same item, skipping reload`);
1713
+ }
1714
+ }
1715
+
1716
+ // Move selected entry to first position in navigation history by identity
1717
+ setNavigationHistory((history) => {
1718
+ const filtered = history.filter((entry) => {
1719
+ const sameView = entry.viewName === selectedEntry.viewName;
1720
+ const bothNoItem = !entry.item && !selectedEntry.item;
1721
+ const sameItem =
1722
+ !!entry.item &&
1723
+ !!selectedEntry.item &&
1724
+ entry.item.id === selectedEntry.item.id &&
1725
+ entry.item.language === selectedEntry.item.language &&
1726
+ entry.item.version === selectedEntry.item.version;
1727
+ return !(sameView && (bothNoItem || sameItem));
1728
+ });
1729
+ return [selectedEntry, ...filtered].slice(0, 25);
1730
+ });
1731
+ }
1732
+ setQuickSwitcherVisible(false);
1733
+ },
1734
+ [
1735
+ navigationHistory,
1736
+ loadItem,
1737
+ switchView,
1738
+ viewName,
1739
+ contentEditorItem,
1740
+ setNavigationHistory,
1741
+ ],
1742
+ );
1743
+
1514
1744
  const { handleKeyDown } = useKeyboardNavigation({
1515
1745
  editContextRef,
1516
1746
  operations,
@@ -1522,10 +1752,57 @@ export function EditorShell({
1522
1752
  showInfoToast,
1523
1753
  showErrorToast,
1524
1754
  executeCommand,
1755
+ showQuickSwitcher,
1756
+ cycleQuickSwitcher,
1525
1757
  });
1526
1758
 
1527
1759
  useGlobalEditorKeyDown(handleKeyDown);
1528
1760
 
1761
+ // Global keydown/keyup handler to manage quick switcher
1762
+ useEffect(() => {
1763
+ const handleKeyDown = (event: KeyboardEvent) => {
1764
+ if (!quickSwitcherVisible) return;
1765
+
1766
+ // Enter to select and close
1767
+ if (event.key === "Enter") {
1768
+ event.preventDefault();
1769
+ handleQuickSwitcherSelect(quickSwitcherSelectedIndex);
1770
+ }
1771
+ };
1772
+
1773
+ const handleKeyUp = (event: KeyboardEvent) => {
1774
+ // Close on Escape without selecting
1775
+ if (event.key === "Escape" && quickSwitcherVisible) {
1776
+ setQuickSwitcherVisible(false);
1777
+ }
1778
+ // Close and select when releasing Ctrl, Alt, or Meta (Win key)
1779
+ if (
1780
+ (event.key === "Control" ||
1781
+ event.key === "Alt" ||
1782
+ event.key === "Meta") &&
1783
+ quickSwitcherVisible
1784
+ ) {
1785
+ // Small delay to ensure the key release is not part of the initial trigger
1786
+ setTimeout(() => {
1787
+ handleQuickSwitcherSelect(quickSwitcherSelectedIndex);
1788
+ }, 50);
1789
+ }
1790
+ };
1791
+
1792
+ if (typeof window !== "undefined") {
1793
+ window.addEventListener("keydown", handleKeyDown);
1794
+ window.addEventListener("keyup", handleKeyUp);
1795
+ return () => {
1796
+ window.removeEventListener("keydown", handleKeyDown);
1797
+ window.removeEventListener("keyup", handleKeyUp);
1798
+ };
1799
+ }
1800
+ }, [
1801
+ quickSwitcherVisible,
1802
+ quickSwitcherSelectedIndex,
1803
+ handleQuickSwitcherSelect,
1804
+ ]);
1805
+
1529
1806
  // Global ctrl+click handler for GUID detection and navigation
1530
1807
  useEffect(() => {
1531
1808
  const isGuid = (text: string): boolean => {
@@ -1614,8 +1891,8 @@ export function EditorShell({
1614
1891
  };
1615
1892
 
1616
1893
  const handleCtrlClick = async (event: globalThis.MouseEvent) => {
1617
- // Only proceed if Ctrl+Shift (or Cmd+Shift on Mac) is pressed
1618
- if ((!event.ctrlKey && !event.metaKey) || !event.shiftKey) return;
1894
+ // Only proceed if Ctrl (or Cmd on Mac) is pressed
1895
+ if (!event.ctrlKey && !event.metaKey) return;
1619
1896
 
1620
1897
  const target = event.target as Element;
1621
1898
  const text = getTextFromElement(target);
@@ -2287,6 +2564,7 @@ export function EditorShell({
2287
2564
  setCurrentWizardId,
2288
2565
  favorites,
2289
2566
  loadFavorites,
2567
+ isQuickSwitcherVisible: quickSwitcherVisible,
2290
2568
  // Context factory registry methods
2291
2569
  registerContextFactory: (
2292
2570
  name: string,
@@ -2736,6 +3014,13 @@ export function EditorShell({
2736
3014
  {dialog}
2737
3015
  <Toaster position="top-center" />{" "}
2738
3016
  <ConfirmationDialog ref={confirmationDialogRef} />
3017
+ <QuickItemSwitcher
3018
+ visible={quickSwitcherVisible}
3019
+ entries={navigationHistory.slice(0, 5)}
3020
+ selectedIndex={quickSwitcherSelectedIndex}
3021
+ onSelect={handleQuickSwitcherSelect}
3022
+ onClose={() => setQuickSwitcherVisible(false)}
3023
+ />
2739
3024
  <EditContextMenu ref={contextMenuRef} />
2740
3025
  {media.mediaSelectorVisible && (
2741
3026
  <MediaSelector
@@ -334,21 +334,6 @@ export function useSocketMessageHandler(deps: {
334
334
  }
335
335
  }
336
336
 
337
- // Agent WebSocket messages - forward to agent terminal via listeners
338
- if (
339
- message.type === "agent:name:updated" ||
340
- message.type === "agent:status:changed" ||
341
- message.type === "agent:run:start" ||
342
- message.type === "agent:user:message" ||
343
- message.type === "agent:run:delta" ||
344
- message.type === "agent:run:status" ||
345
- message.type === "agent:run:complete" ||
346
- message.type === "agent:run:error"
347
- ) {
348
- // These messages are handled by AgentTerminal via socketMessageListeners
349
- // Just pass through to listeners
350
- }
351
-
352
337
  socketMessageListeners.current.forEach((listener) => listener(message));
353
338
  },
354
339
  [currentItemDescriptor, sessionId],
@@ -65,7 +65,7 @@ export function EditorChrome(props: {
65
65
  title: "Agents",
66
66
  content: <Agents />,
67
67
  initialSize: 70,
68
- noOverflow: true,
68
+ noOverflow: false,
69
69
  },
70
70
  ],
71
71
  }}
@@ -48,6 +48,7 @@ export const deleteItemCommand: ItemCommand = {
48
48
  id: "deleteItem",
49
49
  label: "Delete",
50
50
  icon: <Trash2 strokeWidth={1} />,
51
+ keyBinding: "Delete",
51
52
  disabled: (context: ItemCommandContext) =>
52
53
  !context.data?.items || !context.data?.items[0]?.canDelete || false,
53
54
  execute: async (context: ItemCommandContext) => {
@@ -0,0 +1,184 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ applySetup,
4
+ getSetupStatus,
5
+ SetupCategoryDto,
6
+ } from "../../services/setupService";
7
+ import { RefreshCw } from "lucide-react";
8
+
9
+ export function ParheliaSetupOverview() {
10
+ const [categories, setCategories] = useState<SetupCategoryDto[]>([]);
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState<string | null>(null);
13
+ const [applying, setApplying] = useState(false);
14
+ const [includeDeps, setIncludeDeps] = useState(true);
15
+
16
+ const load = async () => {
17
+ try {
18
+ setLoading(true);
19
+ setError(null);
20
+ const status = await getSetupStatus(false);
21
+ setCategories(status.categories || []);
22
+ } catch (e) {
23
+ setError(e instanceof Error ? e.message : "Failed to load setup status");
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ load();
31
+ }, []);
32
+
33
+ return (
34
+ <div className="h-full overflow-auto p-4">
35
+ <div className="mb-4 flex items-center justify-between">
36
+ <div className="text-lg font-semibold text-gray-800">
37
+ Parhelia Setup
38
+ </div>
39
+ <div className="flex items-center gap-2">
40
+ <label className="mr-2 flex items-center gap-2 text-sm text-gray-700">
41
+ <input
42
+ type="checkbox"
43
+ checked={includeDeps}
44
+ onChange={(e) => setIncludeDeps(e.target.checked)}
45
+ />
46
+ Include dependencies
47
+ </label>
48
+ <button
49
+ onClick={load}
50
+ disabled={loading || applying}
51
+ className="flex items-center gap-2 rounded bg-gray-100 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
52
+ >
53
+ <RefreshCw className="h-4 w-4" strokeWidth={1} />
54
+ {loading ? "Refreshing..." : "Refresh"}
55
+ </button>
56
+ <button
57
+ onClick={async () => {
58
+ try {
59
+ setApplying(true);
60
+ setError(null);
61
+ const keys = categories
62
+ .flatMap((c) => c.items)
63
+ .filter((i) => i.status === "Missing")
64
+ .map((i) => i.key);
65
+ if (keys.length === 0) return;
66
+ await applySetup(keys, includeDeps);
67
+ await load();
68
+ } catch (e) {
69
+ setError(
70
+ e instanceof Error ? e.message : "Failed to apply setup",
71
+ );
72
+ } finally {
73
+ setApplying(false);
74
+ }
75
+ }}
76
+ disabled={loading || applying}
77
+ className="flex items-center gap-2 rounded bg-indigo-600 px-3 py-1.5 text-sm text-white hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50"
78
+ >
79
+ {applying ? "Applying..." : "Create Missing"}
80
+ </button>
81
+ <button
82
+ onClick={async () => {
83
+ try {
84
+ setApplying(true);
85
+ setError(null);
86
+ const keys = categories
87
+ .flatMap((c) => c.items)
88
+ .filter((i) => i.status === "Outdated")
89
+ .map((i) => i.key);
90
+ if (keys.length === 0) return;
91
+ await applySetup(keys, includeDeps);
92
+ await load();
93
+ } catch (e) {
94
+ setError(
95
+ e instanceof Error ? e.message : "Failed to apply setup",
96
+ );
97
+ } finally {
98
+ setApplying(false);
99
+ }
100
+ }}
101
+ disabled={loading || applying}
102
+ className="flex items-center gap-2 rounded bg-amber-600 px-3 py-1.5 text-sm text-white hover:bg-amber-700 disabled:cursor-not-allowed disabled:opacity-50"
103
+ >
104
+ {applying ? "Applying..." : "Update Outdated"}
105
+ </button>
106
+ </div>
107
+ </div>
108
+
109
+ {error && (
110
+ <div className="mb-3 rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700">
111
+ {error}
112
+ </div>
113
+ )}
114
+
115
+ <div className="space-y-6">
116
+ {categories.map((cat) => (
117
+ <div key={cat.name}>
118
+ <div className="mb-2 text-sm font-semibold text-gray-700">
119
+ {cat.name}
120
+ </div>
121
+ <div className="divide-y rounded border">
122
+ {cat.items.map((it) => (
123
+ <div
124
+ key={it.key}
125
+ className="flex items-center justify-between px-3 py-2 text-sm"
126
+ >
127
+ <div className="min-w-0">
128
+ <div className="truncate font-medium">{it.key}</div>
129
+ <div className="truncate text-xs text-gray-500">
130
+ {it.targetPath}
131
+ </div>
132
+ </div>
133
+ <div className="ml-3 shrink-0">
134
+ <span
135
+ className={`rounded px-2 py-0.5 text-xs font-medium ${
136
+ it.status === "Missing"
137
+ ? "bg-yellow-100 text-yellow-700"
138
+ : it.status === "UpToDate"
139
+ ? "bg-green-100 text-green-700"
140
+ : it.status === "Outdated"
141
+ ? "bg-amber-100 text-amber-700"
142
+ : it.status === "Modified"
143
+ ? "bg-blue-100 text-blue-700"
144
+ : "bg-gray-100 text-gray-700"
145
+ }`}
146
+ >
147
+ {it.status}
148
+ </span>
149
+ </div>
150
+ <div className="ml-3 shrink-0">
151
+ {it.status === "Missing" || it.status === "Outdated" ? (
152
+ <button
153
+ onClick={async () => {
154
+ try {
155
+ setApplying(true);
156
+ setError(null);
157
+ await applySetup([it.key], includeDeps);
158
+ await load();
159
+ } catch (e) {
160
+ setError(
161
+ e instanceof Error
162
+ ? e.message
163
+ : "Failed to apply setup",
164
+ );
165
+ } finally {
166
+ setApplying(false);
167
+ }
168
+ }}
169
+ disabled={loading || applying}
170
+ className="rounded bg-gray-800 px-2 py-1 text-xs text-white hover:bg-black disabled:cursor-not-allowed disabled:opacity-50"
171
+ >
172
+ {it.status === "Missing" ? "Install" : "Update"}
173
+ </button>
174
+ ) : null}
175
+ </div>
176
+ </div>
177
+ ))}
178
+ </div>
179
+ </div>
180
+ ))}
181
+ </div>
182
+ </div>
183
+ );
184
+ }
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo } from "react";
4
- import { Download, ExternalLink, FileIcon, ImageOff } from "lucide-react";
4
+ import { FileIcon, ImageOff } from "lucide-react";
5
5
 
6
- import { Button } from "../../components/ui/button";
7
6
  import { cn } from "../../lib/utils";
8
7
  import { AttachmentField } from "../fieldTypes";
9
8
 
@@ -86,38 +85,18 @@ export function AttachmentEditor({ field, readOnly }: AttachmentEditorProps) {
86
85
 
87
86
  if (isImage) {
88
87
  return (
89
- <div className="flex flex-col items-start gap-2">
90
- <div
91
- className={cn(
92
- "focus-shadow flex max-h-72 w-full max-w-xs items-center justify-center overflow-hidden rounded border border-gray-300 bg-gray-50",
93
- readOnly && "opacity-80",
94
- )}
95
- >
96
- <img
97
- src={src}
98
- alt={fileName ?? "Attachment"}
99
- className="max-h-72 w-full object-contain"
100
- loading="lazy"
101
- />
102
- </div>
103
- <div className="flex items-center gap-2">
104
- <Button asChild size="xs" variant="secondary">
105
- <a href={src} target="_blank" rel="noopener noreferrer">
106
- <ExternalLink className="h-3.5 w-3.5" strokeWidth={1} /> Open
107
- </a>
108
- </Button>
109
- <Button asChild size="xs" variant="secondary">
110
- <a
111
- href={src}
112
- download={fileName ?? true}
113
- {...(field?.value?.mimeType
114
- ? { type: field.value.mimeType }
115
- : {})}
116
- >
117
- <Download className="h-3.5 w-3.5" strokeWidth={1} /> Download
118
- </a>
119
- </Button>
120
- </div>
88
+ <div
89
+ className={cn(
90
+ "focus-shadow flex max-h-72 w-full max-w-xs items-center justify-center overflow-hidden rounded border border-gray-300 bg-gray-50",
91
+ readOnly && "opacity-80",
92
+ )}
93
+ >
94
+ <img
95
+ src={src}
96
+ alt={fileName ?? "Attachment"}
97
+ className="max-h-72 w-full object-contain"
98
+ loading="lazy"
99
+ />
121
100
  </div>
122
101
  );
123
102
  }
@@ -133,22 +112,6 @@ export function AttachmentEditor({ field, readOnly }: AttachmentEditorProps) {
133
112
  </span>
134
113
  )}
135
114
  </div>
136
- <div className="flex items-center gap-2">
137
- <Button asChild size="xs" variant="secondary">
138
- <a href={src} target="_blank" rel="noopener noreferrer">
139
- <ExternalLink className="h-3.5 w-3.5" strokeWidth={1} /> Open
140
- </a>
141
- </Button>
142
- <Button asChild size="xs" variant="secondary">
143
- <a
144
- href={src}
145
- download={fileName ?? true}
146
- {...(field?.value?.mimeType ? { type: field.value.mimeType } : {})}
147
- >
148
- <Download className="h-3.5 w-3.5" strokeWidth={1} /> Download
149
- </a>
150
- </Button>
151
- </div>
152
115
  </div>
153
116
  );
154
117
  }