@motiadev/workbench 0.9.2-beta.147 → 0.9.4-beta.149

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.
@@ -60,10 +60,6 @@ function shouldInvalidatePlugins(file, plugins) {
60
60
  function handlePluginHotUpdate(ctx, plugins, printer) {
61
61
  const { file, server, timestamp } = ctx;
62
62
  printer.printPluginLog(`HMR: File changed: ${(0, utils_1.normalizePath)(file)}`);
63
- // Check if this change affects plugins
64
- if (!shouldInvalidatePlugins(file, plugins)) {
65
- return; // Let Vite handle it normally
66
- }
67
63
  if (isConfigFile(file)) {
68
64
  printer.printPluginLog('HMR: Config file changed, triggering full page reload');
69
65
  printer.printPluginWarn('Configuration changes require a server restart for full effect. Please restart the dev server to apply all changes.');
@@ -73,11 +69,14 @@ function handlePluginHotUpdate(ctx, plugins, printer) {
73
69
  });
74
70
  return;
75
71
  }
72
+ if (!shouldInvalidatePlugins(file, plugins)) {
73
+ printer.printPluginLog('HMR: Change outside plugin scope, delegating to Vite default handling');
74
+ return;
75
+ }
76
76
  printer.printPluginLog('HMR: Plugin change detected, invalidating virtual module');
77
- // Find the virtual module
78
77
  const virtualModule = server.moduleGraph.getModuleById(types_1.CONSTANTS.RESOLVED_VIRTUAL_MODULE_ID);
79
78
  if (!virtualModule) {
80
- printer.printPluginWarn('HMR: Virtual module not found, triggering full reload');
79
+ printer.printPluginWarn('HMR: Virtual module not found, triggering full reload as fallback');
81
80
  server.ws.send({
82
81
  type: 'full-reload',
83
82
  path: '*',
@@ -86,31 +85,16 @@ function handlePluginHotUpdate(ctx, plugins, printer) {
86
85
  }
87
86
  server.moduleGraph.invalidateModule(virtualModule, new Set(), timestamp);
88
87
  printer.printPluginLog('HMR: Virtual module invalidated');
89
- const modulesToUpdate = [virtualModule];
88
+ const modulesToUpdateSet = new Set([virtualModule]);
90
89
  const processedModules = new Set([virtualModule]);
91
- // Recursively add all importers
92
- const addImporters = (module) => {
93
- for (const importer of module.importers) {
94
- if (!processedModules.has(importer)) {
95
- processedModules.add(importer);
96
- modulesToUpdate.push(importer);
97
- server.moduleGraph.invalidateModule(importer, new Set(), timestamp);
98
- addImporters(importer);
99
- }
90
+ for (const importer of virtualModule.importers) {
91
+ if (!processedModules.has(importer)) {
92
+ processedModules.add(importer);
93
+ modulesToUpdateSet.add(importer);
94
+ server.moduleGraph.invalidateModule(importer, new Set(), timestamp);
100
95
  }
101
- };
102
- addImporters(virtualModule);
103
- server.ws.send({
104
- type: 'update',
105
- updates: modulesToUpdate
106
- .filter((m) => m.url)
107
- .map((m) => ({
108
- type: m.type === 'css' ? 'css-update' : 'js-update',
109
- path: m.url,
110
- acceptedPath: m.url,
111
- timestamp,
112
- })),
113
- });
96
+ }
97
+ const modulesToUpdate = Array.from(modulesToUpdateSet);
114
98
  printer.printPluginLog(`HMR: Updated ${modulesToUpdate.length} module(s)`);
115
99
  return modulesToUpdate;
116
100
  }
@@ -141,8 +141,9 @@ function motiaPluginsPlugin(plugins) {
141
141
  }
142
142
  const modulesToUpdate = (0, hmr_1.handlePluginHotUpdate)(ctx, plugins, printer);
143
143
  if (modulesToUpdate && modulesToUpdate.length > 0) {
144
- printer.printPluginLog(`HMR: Successfully updated ${modulesToUpdate.length} module(s)`);
145
- return modulesToUpdate;
144
+ const merged = Array.from(new Set([...(ctx.modules || []), ...modulesToUpdate]));
145
+ printer.printPluginLog(`HMR: Successfully updated ${merged.length} module(s)`);
146
+ return merged;
146
147
  }
147
148
  },
148
149
  buildEnd() {
package/dist/src/App.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useMemo } from 'react';
2
+ import { memo, useEffect, useMemo } from 'react';
3
3
  import { FlowPage } from './components/flow/flow-page';
4
4
  import { FlowTabMenuItem } from './components/flow/flow-tab-menu-item';
5
5
  import { registerPluginTabs } from './lib/plugins';
@@ -18,7 +18,7 @@ const topTabs = [
18
18
  content: FlowPage,
19
19
  },
20
20
  ];
21
- export const App = () => {
21
+ export const App = memo(() => {
22
22
  const setTabs = useAppTabsStore((state) => state.setTabs);
23
23
  const addTab = useAppTabsStore((state) => state.addTab);
24
24
  useEffect(() => {
@@ -31,4 +31,5 @@ export const App = () => {
31
31
  const viewMode = useMemo(getViewModeFromURL, []);
32
32
  const ViewComponent = viewMode === 'project' ? ProjectViewMode : SystemViewMode;
33
33
  return _jsx(ViewComponent, {});
34
- };
34
+ });
35
+ App.displayName = 'App';
@@ -0,0 +1 @@
1
+ export declare const BottomPanel: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CollapsiblePanel, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
3
+ import { memo, useId } from 'react';
4
+ import { useShallow } from 'zustand/react/shallow';
5
+ import { TabLocation, useAppTabsStore } from '../stores/use-app-tabs-store';
6
+ import { useTabsStore } from '../stores/use-tabs-store';
7
+ const bottomTabsSelector = (state) => state.tabs[TabLocation.BOTTOM];
8
+ export const BottomPanel = memo(() => {
9
+ const defaultTab = useTabsStore((state) => state.tab.bottom);
10
+ const setBottomTab = useTabsStore((state) => state.setBottomTab);
11
+ const tabs = useAppTabsStore(useShallow(bottomTabsSelector));
12
+ const bottomPanelId = useId();
13
+ return (_jsx(CollapsiblePanel, { id: bottomPanelId, variant: 'tabs', defaultTab: defaultTab, onTabChange: setBottomTab, header: _jsx(TabsList, { children: tabs.map(({ id, tabLabel: Label }) => (_jsx(TabsTrigger, { value: id, "data-testid": `${id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(Label, {}) }, id))) }), children: tabs.map(({ id, content: Element }) => (_jsx(TabsContent, { value: id, className: "h-full", children: _jsx(Element, {}) }, id))) }));
14
+ });
15
+ BottomPanel.displayName = 'BottomPanel';
@@ -1 +1 @@
1
- export declare const FlowPage: () => import("react/jsx-runtime").JSX.Element;
1
+ export declare const FlowPage: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -1,21 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useStreamItem } from '@motiadev/stream-client-react';
3
3
  import { ReactFlowProvider } from '@xyflow/react';
4
+ import { memo, useMemo } from 'react';
4
5
  import { useFlowStore } from '@/stores/use-flow-store';
5
6
  import { FlowView } from './flow-view';
6
- export const FlowPage = () => {
7
+ export const FlowPage = memo(() => {
7
8
  const selectedFlowId = useFlowStore((state) => state.selectedFlowId);
8
- const { data: flow } = useStreamItem({
9
- streamName: '__motia.flows',
10
- groupId: 'default',
11
- id: selectedFlowId ?? '',
12
- });
13
- const { data: flowConfig } = useStreamItem({
14
- streamName: '__motia.flowsConfig',
15
- groupId: 'default',
16
- id: selectedFlowId ?? '',
17
- });
9
+ const streamItemArgs = useMemo(() => ({ streamName: '__motia.flows', groupId: 'default', id: selectedFlowId ?? '' }), [selectedFlowId]);
10
+ const { data: flow } = useStreamItem(streamItemArgs);
11
+ const streamItemArgsConfig = useMemo(() => ({ streamName: '__motia.flowsConfig', groupId: 'default', id: selectedFlowId ?? '' }), [selectedFlowId]);
12
+ const { data: flowConfig } = useStreamItem(streamItemArgsConfig);
18
13
  if (!flow || flow.error)
19
14
  return (_jsx("div", { className: "w-full h-full bg-background flex flex-col items-center justify-center", children: _jsx("p", { children: flow?.error }) }));
20
15
  return (_jsx(ReactFlowProvider, { children: _jsx(FlowView, { flow: flow, flowConfig: flowConfig }) }));
21
- };
16
+ });
17
+ FlowPage.displayName = 'FlowPage';
@@ -17,5 +17,5 @@ export const FlowTabMenuItem = () => {
17
17
  selectFlowId(flowId);
18
18
  motiaAnalytics.track('flow_selected', { flow: flowId });
19
19
  };
20
- return (_jsxs("div", { className: "flex flex-row justify-center items-center gap-2 cursor-pointer", children: [_jsx(Workflow, {}), selectedFlowId ?? 'No flow selected', _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("div", { "data-testid": "flows-dropdown-trigger", className: "flex flex-row justify-center items-center gap-2 cursor-pointer", children: _jsx(ChevronsUpDown, { className: "size-4" }) }) }), _jsx(DropdownMenuContent, { className: "bg-background text-foreground flows-dropdown", children: flows.map((item) => (_jsx(DropdownMenuItem, { "data-testid": `dropdown-${item}`, className: "cursor-pointer gap-2 flow-link", onClick: () => handleFlowSelect(item), children: item }, `dropdown-${item}`))) })] })] }));
20
+ return (_jsxs("div", { className: "flex flex-row justify-center items-center gap-2 cursor-pointer", children: [_jsx(Workflow, {}), selectedFlowId ?? 'No flow selected', _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("div", { className: "flex flex-row justify-center items-center gap-2 cursor-pointer", children: _jsx(ChevronsUpDown, { className: "size-4", "data-testid": "flows-dropdown-trigger" }) }) }), _jsx(DropdownMenuContent, { className: "bg-background text-foreground flows-dropdown", children: flows.map((item) => (_jsx(DropdownMenuItem, { "data-testid": `dropdown-${item}`, className: "cursor-pointer gap-2 flow-link", onClick: () => handleFlowSelect(item), children: item }, `dropdown-${item}`))) })] })] }));
21
21
  };
@@ -1,25 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useThemeStore } from '@motiadev/ui';
3
- import { useEffect, useState } from 'react';
3
+ import { memo, useEffect } from 'react';
4
4
  import motiaLogoDark from '@/assets/motia-dark.png';
5
5
  import motiaLogoLight from '@/assets/motia-light.png';
6
+ import { useMotiaConfigStore } from '@/stores/use-motia-config-store';
6
7
  import { Tutorial } from '../tutorial/tutorial';
7
8
  import { TutorialButton } from '../tutorial/tutorial-button';
8
9
  import { ThemeToggle } from '../ui/theme-toggle';
9
10
  import { DeployButton } from './deploy-button';
10
- export const Header = () => {
11
- const [isDevMode, setIsDevMode] = useState(false);
12
- const [isTutorialDisabled, setIsTutorialDisabled] = useState(true);
11
+ export const Header = memo(() => {
13
12
  const theme = useThemeStore((state) => state.theme);
14
13
  const logo = theme === 'light' ? motiaLogoLight : motiaLogoDark;
14
+ const config = useMotiaConfigStore((state) => state.config);
15
+ const fetchConfig = useMotiaConfigStore((state) => state.fetchConfig);
15
16
  useEffect(() => {
16
- fetch('/__motia')
17
- .then((res) => res.json())
18
- .then((data) => {
19
- setIsDevMode(data.isDev);
20
- setIsTutorialDisabled(data.isTutorialDisabled);
21
- })
22
- .catch((err) => console.error(err));
17
+ fetchConfig();
23
18
  }, []);
19
+ const isDevMode = config?.isDev ?? false;
20
+ const isTutorialDisabled = config?.isTutorialDisabled ?? true;
24
21
  return (_jsxs("header", { className: "min-h-16 px-4 gap-4 flex items-center bg-default text-default-foreground border-b", children: [_jsx("img", { src: logo, className: "h-5", id: "logo-icon", "data-testid": "logo-icon" }), _jsx("div", { className: "flex-1" }), _jsx(ThemeToggle, {}), isDevMode && !isTutorialDisabled && _jsx(TutorialButton, {}), isDevMode && _jsx(DeployButton, {}), !isTutorialDisabled && _jsx(Tutorial, {})] }));
25
- };
22
+ });
23
+ Header.displayName = 'Header';
@@ -1,3 +1,2 @@
1
- import type React from 'react';
2
1
  import type { PropsWithChildren } from 'react';
3
2
  export declare const RootMotia: React.FC<PropsWithChildren>;
@@ -1,5 +1,7 @@
1
+ import { memo } from 'react';
1
2
  import { useAnalytics } from '@/lib/motia-analytics';
2
- export const RootMotia = ({ children }) => {
3
+ export const RootMotia = memo(({ children }) => {
3
4
  useAnalytics();
4
5
  return children;
5
- };
6
+ });
7
+ RootMotia.displayName = 'RootMotia';
@@ -0,0 +1 @@
1
+ export declare const TopPanel: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CollapsiblePanel, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
3
+ import { memo, useId } from 'react';
4
+ import { useShallow } from 'zustand/react/shallow';
5
+ import { TabLocation, useAppTabsStore } from '../stores/use-app-tabs-store';
6
+ import { useTabsStore } from '../stores/use-tabs-store';
7
+ const topTabsSelector = (state) => state.tabs[TabLocation.TOP];
8
+ export const TopPanel = memo(() => {
9
+ const defaultTab = useTabsStore((state) => state.tab.top);
10
+ const setTopTab = useTabsStore((state) => state.setTopTab);
11
+ const tabs = useAppTabsStore(useShallow(topTabsSelector));
12
+ const topPanelId = useId();
13
+ return (_jsx(CollapsiblePanel, { id: topPanelId, variant: 'tabs', defaultTab: defaultTab, onTabChange: setTopTab, header: _jsx(TabsList, { children: tabs.map(({ id, tabLabel: Label }) => (_jsx(TabsTrigger, { value: id, "data-testid": `${id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(Label, {}) }, id))) }), children: tabs.map(({ id, content: Element }) => (_jsx(TabsContent, { value: id, className: "h-full", children: _jsx(Element, {}) }, id))) }));
14
+ });
15
+ TopPanel.displayName = 'TopPanel';
@@ -1,14 +1,12 @@
1
1
  import { useStreamGroup } from '@motiadev/stream-client-react';
2
2
  import { useEffect } from 'react';
3
3
  import { useFlowStore } from '@/stores/use-flow-store';
4
+ const streamGroupArgs = { streamName: '__motia.flows', groupId: 'default' };
4
5
  export const useFetchFlows = () => {
5
6
  const setFlows = useFlowStore((state) => state.setFlows);
6
7
  const selectFlowId = useFlowStore((state) => state.selectFlowId);
7
8
  const selectedFlowId = useFlowStore((state) => state.selectedFlowId);
8
- const { data: flows } = useStreamGroup({
9
- streamName: '__motia.flows',
10
- groupId: 'default',
11
- });
9
+ const { data: flows } = useStreamGroup(streamGroupArgs);
12
10
  useEffect(() => {
13
11
  if (flows)
14
12
  setFlows(flows.map((flow) => flow.id));
@@ -18,5 +16,5 @@ export const useFetchFlows = () => {
18
16
  (selectedFlowId && flows.length > 0 && !flows.find((flow) => flow.id === selectedFlowId))) {
19
17
  selectFlowId(flows[0].id);
20
18
  }
21
- }, [flows, selectedFlowId, selectFlowId, selectedFlowId]);
19
+ }, [flows, selectedFlowId, selectFlowId]);
22
20
  };
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { plugins } from 'virtual:motia-plugins';
3
3
  import { DynamicIcon, dynamicIconImports } from 'lucide-react/dynamic';
4
4
  import { memo } from 'react';
5
- import { TabLocation } from '@/stores/use-app-tabs-store';
5
+ import { TabLocation, useAppTabsStore } from '@/stores/use-app-tabs-store';
6
6
  import { isValidTabLocation } from './utils';
7
7
  export const registerPluginTabs = (addTab) => {
8
8
  if (!Array.isArray(plugins)) {
@@ -53,3 +53,53 @@ export const registerPluginTabs = (addTab) => {
53
53
  }
54
54
  });
55
55
  };
56
+ const refreshPluginTabs = (nextPlugins) => {
57
+ try {
58
+ const state = useAppTabsStore.getState();
59
+ const { removeTab, addTab } = state;
60
+ const idsToRefresh = new Set(nextPlugins.map((p) => (p.label || '').toLowerCase()));
61
+ idsToRefresh.forEach((id) => {
62
+ removeTab(TabLocation.TOP, id);
63
+ removeTab(TabLocation.BOTTOM, id);
64
+ });
65
+ nextPlugins.forEach((plugin, index) => {
66
+ try {
67
+ if (!plugin.label || !plugin.component)
68
+ return;
69
+ const position = plugin.position || 'top';
70
+ const tabLocation = isValidTabLocation(position) ? position : TabLocation.TOP;
71
+ const PluginTabLabel = memo(() => {
72
+ const hasIcon = Object.keys(dynamicIconImports).includes(plugin.labelIcon);
73
+ const iconName = hasIcon ? plugin.labelIcon : 'toy-brick';
74
+ return (_jsxs(_Fragment, { children: [_jsx(DynamicIcon, { name: iconName }), _jsx("span", { children: plugin.label })] }));
75
+ });
76
+ PluginTabLabel.displayName = `${plugin.label}TabLabel_HMR_${index}`;
77
+ const PluginContent = memo(() => {
78
+ const Component = plugin.component;
79
+ const props = plugin.props || {};
80
+ if (!Component)
81
+ return _jsx("div", { children: "Error: Plugin component not found" });
82
+ return _jsx(Component, { ...props });
83
+ });
84
+ PluginContent.displayName = `${plugin.label}Content_HMR_${index}`;
85
+ addTab(tabLocation, {
86
+ id: plugin.label.toLowerCase(),
87
+ tabLabel: PluginTabLabel,
88
+ content: PluginContent,
89
+ });
90
+ }
91
+ catch (error) {
92
+ console.error(`[Motia] Error refreshing plugin "${plugin.label}":`, error);
93
+ }
94
+ });
95
+ }
96
+ catch (err) {
97
+ console.error('[Motia] Failed to refresh plugin tabs via HMR:', err);
98
+ }
99
+ };
100
+ if (import.meta.hot) {
101
+ import.meta.hot.accept('virtual:motia-plugins', (mod) => {
102
+ const next = mod?.plugins || [];
103
+ refreshPluginTabs(next);
104
+ });
105
+ }
@@ -1 +1 @@
1
- export declare const ProjectViewMode: () => import("react/jsx-runtime").JSX.Element;
1
+ export declare const ProjectViewMode: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { APP_SIDEBAR_CONTAINER_ID, Panel } from '@motiadev/ui';
3
+ import { memo } from 'react';
3
4
  import { useShallow } from 'zustand/react/shallow';
4
5
  import { TabLocation, useAppTabsStore } from './stores/use-app-tabs-store';
5
6
  const topTabs = (state) => state.tabs[TabLocation.TOP];
6
- export const ProjectViewMode = () => {
7
+ export const ProjectViewMode = memo(() => {
7
8
  const tabs = useAppTabsStore(useShallow(topTabs));
8
- return (_jsxs("div", { className: "grid grid-rows-1 grid-cols-[1fr_auto] bg-background text-foreground h-screen ", children: [_jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsx(Panel, { contentClassName: 'p-0', tabs: tabs.map((tab) => {
9
+ return (_jsxs("div", { className: "grid grid-rows-1 grid-cols-[1fr_auto] bg-background text-foreground h-screen ", children: [_jsx("main", { className: "m-2 overflow-hidden", children: _jsx(Panel, { contentClassName: 'p-0', tabs: tabs.map((tab) => {
9
10
  const Element = tab.content;
10
11
  const LabelComponent = tab.tabLabel;
11
12
  return {
@@ -15,4 +16,5 @@ export const ProjectViewMode = () => {
15
16
  'data-testid': tab.id,
16
17
  };
17
18
  }) }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
18
- };
19
+ });
20
+ ProjectViewMode.displayName = 'ProjectViewMode';
@@ -0,0 +1,12 @@
1
+ interface MotiaConfig {
2
+ isDev: boolean;
3
+ isTutorialDisabled: boolean;
4
+ }
5
+ interface MotiaConfigState {
6
+ config: MotiaConfig | null;
7
+ isLoading: boolean;
8
+ error: Error | null;
9
+ fetchConfig: () => Promise<void>;
10
+ }
11
+ export declare const useMotiaConfigStore: import("zustand").UseBoundStore<import("zustand").StoreApi<MotiaConfigState>>;
12
+ export {};
@@ -0,0 +1,24 @@
1
+ import { create } from 'zustand';
2
+ export const useMotiaConfigStore = create((set, get) => ({
3
+ config: null,
4
+ isLoading: false,
5
+ error: null,
6
+ fetchConfig: async () => {
7
+ const { config, isLoading } = get();
8
+ if (isLoading || config)
9
+ return;
10
+ set({ isLoading: true, error: null });
11
+ try {
12
+ const response = await fetch('/__motia');
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch Motia config: ${response.statusText}`);
15
+ }
16
+ const data = await response.json();
17
+ set({ config: data, isLoading: false });
18
+ }
19
+ catch (error) {
20
+ console.error('Failed to fetch Motia config:', error);
21
+ set({ error: error, isLoading: false });
22
+ }
23
+ },
24
+ }));
@@ -1,14 +1,21 @@
1
1
  import { create } from 'zustand';
2
2
  import { createJSONStorage, persist } from 'zustand/middleware';
3
- export const useTabsStore = create(persist((set) => ({
3
+ import { motiaAnalytics } from '@/lib/motia-analytics';
4
+ export const useTabsStore = create(persist((set, get) => ({
4
5
  tab: {
5
6
  top: 'flow',
6
7
  bottom: 'tracing',
7
8
  },
8
9
  setTopTab: (tab) => {
10
+ const currentTab = get().tab;
11
+ motiaAnalytics.track('Top panel tab changed', { 'new.top': tab, tab: currentTab });
9
12
  set((state) => ({ tab: { ...state.tab, top: tab } }));
10
13
  },
11
- setBottomTab: (tab) => set((state) => ({ tab: { ...state.tab, bottom: tab } })),
14
+ setBottomTab: (tab) => {
15
+ const currentTab = get().tab;
16
+ motiaAnalytics.track('Bottom panel tab changed', { 'new.bottom': tab, tab: currentTab });
17
+ set((state) => ({ tab: { ...state.tab, bottom: tab } }));
18
+ },
12
19
  }), {
13
20
  name: 'motia-tabs-storage',
14
21
  storage: createJSONStorage(() => localStorage),
@@ -1 +1 @@
1
- export declare const SystemViewMode: () => import("react/jsx-runtime").JSX.Element;
1
+ export declare const SystemViewMode: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
@@ -1,36 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { APP_SIDEBAR_CONTAINER_ID, CollapsiblePanel, CollapsiblePanelGroup, TabsContent, TabsList, TabsTrigger, } from '@motiadev/ui';
3
- import { useCallback, useMemo } from 'react';
4
- import { useShallow } from 'zustand/react/shallow';
2
+ import { APP_SIDEBAR_CONTAINER_ID, CollapsiblePanelGroup } from '@motiadev/ui';
3
+ import { memo } from 'react';
4
+ import { BottomPanel } from './components/bottom-panel';
5
5
  import { Header } from './components/header/header';
6
- import { motiaAnalytics } from './lib/motia-analytics';
7
- import { TabLocation, useAppTabsStore } from './stores/use-app-tabs-store';
8
- import { useTabsStore } from './stores/use-tabs-store';
9
- const topTabs = (state) => state.tabs;
10
- export const SystemViewMode = () => {
11
- const tab = useTabsStore((state) => state.tab);
12
- const setTopTab = useTabsStore((state) => state.setTopTab);
13
- const setBottomTab = useTabsStore((state) => state.setBottomTab);
14
- const tabs = useAppTabsStore(useShallow(topTabs));
15
- const tabChangeCallbacks = useMemo(() => ({
16
- [TabLocation.TOP]: setTopTab,
17
- [TabLocation.BOTTOM]: setBottomTab,
18
- }), [setTopTab, setBottomTab]);
19
- const onTabChange = useCallback((location) => (newTab) => {
20
- motiaAnalytics.track(`${location} tab changed`, { [`new.${location}`]: newTab, tab });
21
- tabChangeCallbacks[location](newTab);
22
- }, [tabChangeCallbacks, tab]);
23
- return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsx(CollapsiblePanel, { id: "top-panel", variant: 'tabs', defaultTab: tab.top, onTabChange: onTabChange(TabLocation.TOP), header: _jsx(TabsList, { children: tabs[TabLocation.TOP].map((tab) => {
24
- const LabelComponent = tab.tabLabel;
25
- return (_jsx(TabsTrigger, { value: tab.id, "data-testid": `${tab.id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(LabelComponent, {}) }, tab.id));
26
- }) }), children: tabs[TabLocation.TOP].map((tab) => {
27
- const Element = tab.content;
28
- return (_jsx(TabsContent, { value: tab.id, className: "h-full", children: _jsx(Element, {}) }, tab.id));
29
- }) }), _jsx(CollapsiblePanel, { id: "bottom-panel", variant: 'tabs', defaultTab: tab.bottom, onTabChange: onTabChange(TabLocation.BOTTOM), header: _jsx(TabsList, { children: tabs[TabLocation.BOTTOM].map((tab) => {
30
- const LabelComponent = tab.tabLabel;
31
- return (_jsx(TabsTrigger, { value: tab.id, "data-testid": `${tab.id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(LabelComponent, {}) }, tab.id));
32
- }) }), children: tabs[TabLocation.BOTTOM].map((tab) => {
33
- const Element = tab.content;
34
- return (_jsx(TabsContent, { value: tab.id, className: "h-full", children: _jsx(Element, {}) }, tab.id));
35
- }) })] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
36
- };
6
+ import { TopPanel } from './components/top-panel';
7
+ export const SystemViewMode = memo(() => {
8
+ return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsx(TopPanel, {}), _jsx(BottomPanel, {})] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
9
+ });
10
+ SystemViewMode.displayName = 'SystemViewMode';