feat: phase 5 refactor
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -217,8 +217,8 @@ Pure tab-policy helpers that decide:
|
||||
- [x] Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing).
|
||||
|
||||
## Phase 5 — Menu/Event Harmonization
|
||||
- Route menu-driven view/tab actions through the same behavior layer where applicable.
|
||||
- Reduce drift between keyboard/menu/toolbar interactions.
|
||||
- [x] Route menu-driven view/tab actions through the same behavior layer where applicable.
|
||||
- [x] Reduce drift between keyboard/menu/toolbar interactions.
|
||||
|
||||
## Phase 6 — Cleanup + Exception Review
|
||||
- Reassess every exception in this file and decide keep/remove.
|
||||
@@ -259,4 +259,6 @@ Pure tab-policy helpers that decide:
|
||||
- [x] Phase 3 completion slice: centralized `chat` and `import` tab-open specs and migrated sidebar call sites.
|
||||
- [x] Phase 3 completion slice: centralized `git-diff` file/commit ID/spec/open helpers and reused shared parsing in `TabBar`.
|
||||
- [x] Phase 4 complete: introduced `editorRouting` registry/resolver and migrated `Editor` to declarative route->view rendering.
|
||||
- [ ] Next implementation slice: Phase 5 (menu/event harmonization).
|
||||
- [x] Phase 5 complete: introduced shared activity action executor and reused it in `ActivityBar` and menu view handlers.
|
||||
- [x] Phase 5 complete: `menu:viewPosts` and `menu:viewMedia` now follow the same action pipeline as activity-bar clicks.
|
||||
- [ ] Next implementation slice: Phase 6 (cleanup + exception review).
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useAppStore, PostData, MediaData, TaskProgress } from './store';
|
||||
import { loadTabsForProject, saveTabsForProject } from './utils';
|
||||
import { openSingletonToolTab } from './navigation/tabPolicy';
|
||||
import { persistSiteValidationReport } from './navigation/siteValidationPersistence';
|
||||
import { executeActivityClick } from './navigation/activityExecution';
|
||||
import { ensureRendererPicoThemeStylesheet, getRendererPicoTheme } from './utils/picoTheme';
|
||||
import { useI18n } from './i18n';
|
||||
import './App.css';
|
||||
@@ -216,13 +217,39 @@ const App: React.FC = () => {
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('menu:viewPosts', () => {
|
||||
setActiveView('posts');
|
||||
const state = useAppStore.getState();
|
||||
executeActivityClick(
|
||||
{
|
||||
activeView: state.activeView,
|
||||
sidebarVisible: state.sidebarVisible,
|
||||
tabs: state.tabs,
|
||||
activeTabId: state.activeTabId,
|
||||
},
|
||||
'posts',
|
||||
{
|
||||
setActiveView: state.setActiveView,
|
||||
toggleSidebar: state.toggleSidebar,
|
||||
},
|
||||
);
|
||||
}) || (() => {})
|
||||
);
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('menu:viewMedia', () => {
|
||||
setActiveView('media');
|
||||
const state = useAppStore.getState();
|
||||
executeActivityClick(
|
||||
{
|
||||
activeView: state.activeView,
|
||||
sidebarVisible: state.sidebarVisible,
|
||||
tabs: state.tabs,
|
||||
activeTabId: state.activeTabId,
|
||||
},
|
||||
'media',
|
||||
{
|
||||
setActiveView: state.setActiveView,
|
||||
toggleSidebar: state.toggleSidebar,
|
||||
},
|
||||
);
|
||||
}) || (() => {})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from 'react';
|
||||
import { useAppStore } from '../../store';
|
||||
import { useI18n } from '../../i18n';
|
||||
import {
|
||||
getActivityClickActions,
|
||||
getActivityConfig,
|
||||
isActivityActive,
|
||||
type ActivityId,
|
||||
type ActivitySnapshot,
|
||||
} from '../../navigation/activityBehavior';
|
||||
import { executeActivityClick as runActivityClick } from '../../navigation/activityExecution';
|
||||
import './ActivityBar.css';
|
||||
|
||||
// Simple SVG icons
|
||||
@@ -74,15 +74,10 @@ export const ActivityBar: React.FC = () => {
|
||||
};
|
||||
|
||||
const executeActivityClick = (activityId: ActivityId) => {
|
||||
const actions = getActivityClickActions(snapshot, activityId);
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.type === 'toggleSidebar') {
|
||||
toggleSidebar();
|
||||
} else if (action.type === 'setActiveView') {
|
||||
setActiveView(action.view);
|
||||
}
|
||||
}
|
||||
runActivityClick(snapshot, activityId, {
|
||||
toggleSidebar,
|
||||
setActiveView,
|
||||
});
|
||||
};
|
||||
|
||||
const getTitle = (activityId: ActivityId) => `${t(getActivityConfig(activityId).labelKey)} ${t('activity.toggleHint')}`;
|
||||
|
||||
34
src/renderer/navigation/activityExecution.ts
Normal file
34
src/renderer/navigation/activityExecution.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
getActivityClickActions,
|
||||
type ActivityAction,
|
||||
type ActivityId,
|
||||
type ActivitySnapshot,
|
||||
} from './activityBehavior';
|
||||
import type { SidebarView } from './sidebarViewRegistry';
|
||||
|
||||
export interface ActivityExecutionHandlers {
|
||||
setActiveView: (view: SidebarView) => void;
|
||||
toggleSidebar: () => void;
|
||||
}
|
||||
|
||||
export function executeActivityClickActions(
|
||||
actions: ActivityAction[],
|
||||
handlers: ActivityExecutionHandlers,
|
||||
): void {
|
||||
for (const action of actions) {
|
||||
if (action.type === 'toggleSidebar') {
|
||||
handlers.toggleSidebar();
|
||||
continue;
|
||||
}
|
||||
|
||||
handlers.setActiveView(action.view);
|
||||
}
|
||||
}
|
||||
|
||||
export function executeActivityClick(
|
||||
snapshot: ActivitySnapshot,
|
||||
activityId: ActivityId,
|
||||
handlers: ActivityExecutionHandlers,
|
||||
): void {
|
||||
executeActivityClickActions(getActivityClickActions(snapshot, activityId), handlers);
|
||||
}
|
||||
41
tests/renderer/navigation/activityExecution.test.ts
Normal file
41
tests/renderer/navigation/activityExecution.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
executeActivityClick,
|
||||
executeActivityClickActions,
|
||||
} from '../../../src/renderer/navigation/activityExecution';
|
||||
import type { ActivityAction, ActivitySnapshot } from '../../../src/renderer/navigation/activityBehavior';
|
||||
|
||||
const snapshot: ActivitySnapshot = {
|
||||
activeView: 'media',
|
||||
sidebarVisible: false,
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
};
|
||||
|
||||
describe('activityExecution', () => {
|
||||
it('applies action descriptors in order', () => {
|
||||
const actions: ActivityAction[] = [
|
||||
{ type: 'setActiveView', view: 'posts' },
|
||||
{ type: 'toggleSidebar' },
|
||||
];
|
||||
|
||||
const setActiveView = vi.fn();
|
||||
const toggleSidebar = vi.fn();
|
||||
|
||||
executeActivityClickActions(actions, { setActiveView, toggleSidebar });
|
||||
|
||||
expect(setActiveView).toHaveBeenCalledWith('posts');
|
||||
expect(toggleSidebar).toHaveBeenCalledTimes(1);
|
||||
expect(setActiveView.mock.invocationCallOrder[0]).toBeLessThan(toggleSidebar.mock.invocationCallOrder[0]);
|
||||
});
|
||||
|
||||
it('executes canonical click behavior from activity snapshot', () => {
|
||||
const setActiveView = vi.fn();
|
||||
const toggleSidebar = vi.fn();
|
||||
|
||||
executeActivityClick(snapshot, 'posts', { setActiveView, toggleSidebar });
|
||||
|
||||
expect(setActiveView).toHaveBeenCalledWith('posts');
|
||||
expect(toggleSidebar).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user