feat: F021 market intelligence & signal discovery

Signal domain types, mock data (8 signals), provider interface + MockupMarketIntelligenceProvider, marketIntelligenceService, TanStack Query hooks, 10 ops components (SignalInbox, MarketSignalCard, MarketSignalFilterBar, MarketSignalSkeleton, MarketSignalEmptyState, SignalConfidenceBadge, SourceReliabilityBadge, SensitivityWarningPanel, SignalConversionPanel, SignalEvidenceList), MarketIntelligence page wired into AppShell nav and App router.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-15 17:07:58 +02:00
parent 3cd2e83b25
commit cb18f3c381
17 changed files with 1389 additions and 4 deletions
+43 -4
View File
@@ -1,22 +1,61 @@
import { create } from 'zustand'
import { WorkspaceType } from '../domain/enums'
export const RightPanelContentType = {
AI_CONTEXT: 'ai_context',
DETAIL_PREVIEW: 'detail_preview',
COMPARE_PREVIEW: 'compare_preview',
ACTIVITY_FEED: 'activity_feed',
} as const
export type RightPanelContentType = typeof RightPanelContentType[keyof typeof RightPanelContentType]
interface LayoutState {
activeWorkspace: WorkspaceType
sidebarCollapsed: boolean
pinnedPanels: string[]
isRightPanelOpen: boolean
rightPanelContentType: RightPanelContentType | null
compareTrayVisible: boolean
selectedResultId: string | null
notificationsOpen: boolean
// Actions
setActiveWorkspace: (workspace: WorkspaceType) => void
toggleSidebar: () => void
pinPanel: (panelId: string) => void
unpinPanel: (panelId: string) => void
openRightPanel: (type: RightPanelContentType) => void
closeRightPanel: () => void
toggleRightPanel: (type: RightPanelContentType) => void
setCompareTrayVisible: (visible: boolean) => void
setSelectedResultId: (id: string | null) => void
toggleNotifications: () => void
}
export const useLayoutStore = create<LayoutState>((set) => ({
export const useLayoutStore = create<LayoutState>((set, get) => ({
activeWorkspace: WorkspaceType.SUPPLY,
sidebarCollapsed: false,
pinnedPanels: [],
isRightPanelOpen: false,
rightPanelContentType: null,
compareTrayVisible: false,
selectedResultId: null,
notificationsOpen: false,
setActiveWorkspace: (workspace) => set({ activeWorkspace: workspace }),
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
pinPanel: (panelId) => set((state) => ({ pinnedPanels: [...state.pinnedPanels, panelId] })),
unpinPanel: (panelId) => set((state) => ({ pinnedPanels: state.pinnedPanels.filter(id => id !== panelId) })),
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
pinPanel: (panelId) => set((s) => ({ pinnedPanels: [...s.pinnedPanels, panelId] })),
unpinPanel: (panelId) => set((s) => ({ pinnedPanels: s.pinnedPanels.filter(id => id !== panelId) })),
openRightPanel: (type) => set({ isRightPanelOpen: true, rightPanelContentType: type }),
closeRightPanel: () => set({ isRightPanelOpen: false, rightPanelContentType: null }),
toggleRightPanel: (type) => {
const { isRightPanelOpen, rightPanelContentType } = get()
if (isRightPanelOpen && rightPanelContentType === type) {
set({ isRightPanelOpen: false, rightPanelContentType: null })
} else {
set({ isRightPanelOpen: true, rightPanelContentType: type })
}
},
setCompareTrayVisible: (visible) => set({ compareTrayVisible: visible }),
setSelectedResultId: (id) => set({ selectedResultId: id }),
toggleNotifications: () => set((s) => ({ notificationsOpen: !s.notificationsOpen })),
}))