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).
|
- [x] Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing).
|
||||||
|
|
||||||
## Phase 5 — Menu/Event Harmonization
|
## Phase 5 — Menu/Event Harmonization
|
||||||
- Route menu-driven view/tab actions through the same behavior layer where applicable.
|
- [x] Route menu-driven view/tab actions through the same behavior layer where applicable.
|
||||||
- Reduce drift between keyboard/menu/toolbar interactions.
|
- [x] Reduce drift between keyboard/menu/toolbar interactions.
|
||||||
|
|
||||||
## Phase 6 — Cleanup + Exception Review
|
## Phase 6 — Cleanup + Exception Review
|
||||||
- Reassess every exception in this file and decide keep/remove.
|
- 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 `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 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.
|
- [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 { loadTabsForProject, saveTabsForProject } from './utils';
|
||||||
import { openSingletonToolTab } from './navigation/tabPolicy';
|
import { openSingletonToolTab } from './navigation/tabPolicy';
|
||||||
import { persistSiteValidationReport } from './navigation/siteValidationPersistence';
|
import { persistSiteValidationReport } from './navigation/siteValidationPersistence';
|
||||||
|
import { executeActivityClick } from './navigation/activityExecution';
|
||||||
import { ensureRendererPicoThemeStylesheet, getRendererPicoTheme } from './utils/picoTheme';
|
import { ensureRendererPicoThemeStylesheet, getRendererPicoTheme } from './utils/picoTheme';
|
||||||
import { useI18n } from './i18n';
|
import { useI18n } from './i18n';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
@@ -216,13 +217,39 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
window.electronAPI?.on('menu:viewPosts', () => {
|
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(
|
unsubscribers.push(
|
||||||
window.electronAPI?.on('menu:viewMedia', () => {
|
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 { useAppStore } from '../../store';
|
||||||
import { useI18n } from '../../i18n';
|
import { useI18n } from '../../i18n';
|
||||||
import {
|
import {
|
||||||
getActivityClickActions,
|
|
||||||
getActivityConfig,
|
getActivityConfig,
|
||||||
isActivityActive,
|
isActivityActive,
|
||||||
type ActivityId,
|
type ActivityId,
|
||||||
type ActivitySnapshot,
|
type ActivitySnapshot,
|
||||||
} from '../../navigation/activityBehavior';
|
} from '../../navigation/activityBehavior';
|
||||||
|
import { executeActivityClick as runActivityClick } from '../../navigation/activityExecution';
|
||||||
import './ActivityBar.css';
|
import './ActivityBar.css';
|
||||||
|
|
||||||
// Simple SVG icons
|
// Simple SVG icons
|
||||||
@@ -74,15 +74,10 @@ export const ActivityBar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const executeActivityClick = (activityId: ActivityId) => {
|
const executeActivityClick = (activityId: ActivityId) => {
|
||||||
const actions = getActivityClickActions(snapshot, activityId);
|
runActivityClick(snapshot, activityId, {
|
||||||
|
toggleSidebar,
|
||||||
for (const action of actions) {
|
setActiveView,
|
||||||
if (action.type === 'toggleSidebar') {
|
});
|
||||||
toggleSidebar();
|
|
||||||
} else if (action.type === 'setActiveView') {
|
|
||||||
setActiveView(action.view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTitle = (activityId: ActivityId) => `${t(getActivityConfig(activityId).labelKey)} ${t('activity.toggleHint')}`;
|
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