T1/Phase 1: scaffold Vite SPA + Hono API + Bun worker

- apps/web: Vite + React 19 + TanStack Router/Query + shadcn/ui
   - apps/api: Hono + Bun on :3001 with /api/health, /api/auth/*, /mcp stubs
   - apps/worker: Bun worker stub, DB connection, graceful SIGTERM
   - apps/web-legacy/: old Next.js code moved aside (preserved for T2-T8 reference)
   - Dockerfiles: api (Bun), worker (Bun), spa (multi-stage Caddy)
   - Caddyfile: serves dist + reverse-proxies /api/* + /mcp to api
   - docker-compose.yml: 4-service target (api, spa, db, worker)
   - packages/db/src/client.ts: shared Drizzle client for api + worker
   - db/client.ts: root-level alias for convenience

   Parent: t_e1cbd87d -> t_24c9c3fd (T0)
This commit is contained in:
Hermes
2026-08-01 01:15:31 +00:00
parent 9203aee758
commit fca56ab77e
312 changed files with 3489 additions and 196 deletions
+7
View File
@@ -0,0 +1,7 @@
export { useThemeStore } from './use-theme-store';
export { useSidebarStore } from './use-sidebar-store';
export { useDashboardStore } from './use-dashboard-store';
export { useTimerStore } from './use-timer-store';
export { useFilterStore } from './use-filter-store';
export { useKeyboardShortcutsStore } from './use-keyboard-shortcuts-store';
export { useCreateDialogStore } from './use-create-dialog-store';
@@ -0,0 +1,17 @@
import { create } from "zustand";
type ItemType = "task" | "project" | "habit" | null;
interface CreateDialogState {
type: ItemType;
open: boolean;
openCreate: (type: ItemType) => void;
closeCreate: () => void;
}
export const useCreateDialogStore = create<CreateDialogState>((set) => ({
type: null,
open: false,
openCreate: (type: ItemType) => set({ type, open: true }),
closeCreate: () => set({ type: null, open: false }),
}));
@@ -0,0 +1,62 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface WidgetConfig {
id: string;
type: string;
x: number;
y: number;
w: number;
h: number;
visible: boolean;
}
interface DashboardState {
widgets: WidgetConfig[];
// Actions
setWidgets: (widgets: WidgetConfig[]) => void;
updateWidget: (id: string, updates: Partial<WidgetConfig>) => void;
addWidget: (widget: WidgetConfig) => void;
removeWidget: (id: string) => void;
resetLayout: () => void;
}
const defaultWidgets: WidgetConfig[] = [
{ id: 'today-tasks', type: "Today's Tasks", x: 0, y: 0, w: 6, h: 4, visible: true },
{ id: 'habit-checklist', type: 'Habit Checklist', x: 6, y: 0, w: 3, h: 4, visible: true },
{ id: 'weekly-stats', type: 'Weekly Stats', x: 9, y: 0, w: 3, h: 4, visible: true },
{ id: 'project-progress', type: 'Project Progress', x: 0, y: 4, w: 4, h: 3, visible: true },
{ id: 'upcoming-calendar', type: 'Upcoming Calendar', x: 4, y: 4, w: 4, h: 3, visible: true },
{ id: 'recent-notes', type: 'Recent Notes', x: 8, y: 4, w: 4, h: 3, visible: true },
{ id: 'activity-feed', type: 'Activity Feed', x: 0, y: 7, w: 6, h: 3, visible: true },
{ id: 'quick-capture', type: 'Quick Capture', x: 6, y: 7, w: 3, h: 3, visible: true },
];
export const useDashboardStore = create<DashboardState>()(
persist(
(set) => ({
widgets: defaultWidgets,
setWidgets: (widgets) => set({ widgets }),
updateWidget: (id, updates) =>
set((state) => ({
widgets: state.widgets.map((w) =>
w.id === id ? { ...w, ...updates } : w
),
})),
addWidget: (widget) =>
set((state) => ({
widgets: [...state.widgets, widget],
})),
removeWidget: (id) =>
set((state) => ({
widgets: state.widgets.filter((w) => w.id !== id),
})),
resetLayout: () => set({ widgets: defaultWidgets }),
}),
{
name: 'project-e-dashboard',
}
)
);
@@ -0,0 +1,57 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface FilterState {
domain: string | null;
project: string | null;
tags: string[];
status: string | null;
priority: string | null;
// Actions
setDomain: (domain: string | null) => void;
setProject: (project: string | null) => void;
setTags: (tags: string[]) => void;
addTag: (tag: string) => void;
removeTag: (tag: string) => void;
setStatus: (status: string | null) => void;
setPriority: (priority: string | null) => void;
clearAll: () => void;
}
export const useFilterStore = create<FilterState>()(
persist(
(set) => ({
domain: null,
project: null,
tags: [],
status: null,
priority: null,
setDomain: (domain) => set({ domain }),
setProject: (project) => set({ project }),
setTags: (tags) => set({ tags }),
addTag: (tag) =>
set((state) => ({
tags: state.tags.includes(tag) ? state.tags : [...state.tags, tag],
})),
removeTag: (tag) =>
set((state) => ({
tags: state.tags.filter((t) => t !== tag),
})),
setStatus: (status) => set({ status }),
setPriority: (priority) => set({ priority }),
clearAll: () =>
set({
domain: null,
project: null,
tags: [],
status: null,
priority: null,
}),
}),
{
name: 'project-e-filters',
}
)
);
@@ -0,0 +1,61 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface Shortcut {
key: string;
description: string;
action: string;
enabled: boolean;
}
interface KeyboardShortcutsState {
enabled: boolean;
shortcuts: Shortcut[];
// Actions
setEnabled: (enabled: boolean) => void;
updateShortcut: (key: string, updates: Partial<Shortcut>) => void;
resetShortcuts: () => void;
}
const defaultShortcuts: Shortcut[] = [
{ key: 'g+d', description: 'Go to Dashboard', action: 'navigate_dashboard', enabled: true },
{ key: 'g+t', description: 'Go to Tasks', action: 'navigate_tasks', enabled: true },
{ key: 'g+h', description: 'Go to Habits', action: 'navigate_habits', enabled: true },
{ key: 'g+p', description: 'Go to Projects', action: 'navigate_projects', enabled: true },
{ key: 'g+n', description: 'Go to Notes', action: 'navigate_notes', enabled: true },
{ key: 'g+r', description: 'Go to Reports', action: 'navigate_reports', enabled: true },
{ key: 'g+c', description: 'Go to Calendar', action: 'navigate_calendar', enabled: true },
{ key: 'g+a', description: 'Go to Analytics', action: 'navigate_analytics', enabled: true },
{ key: '/', description: 'Focus search', action: 'focus_search', enabled: true },
{ key: 'n', description: 'Quick add', action: 'quick_add', enabled: true },
{ key: '?', description: 'Show shortcuts help', action: 'show_help', enabled: true },
{ key: 'j', description: 'Move down', action: 'move_down', enabled: true },
{ key: 'k', description: 'Move up', action: 'move_up', enabled: true },
{ key: 'enter', description: 'Open selected', action: 'open', enabled: true },
{ key: 'e', description: 'Edit selected', action: 'edit', enabled: true },
{ key: 'space', description: 'Toggle complete', action: 'toggle_complete', enabled: true },
{ key: 'c', description: 'Complete selected', action: 'complete', enabled: true },
{ key: 'x', description: 'Cancel', action: 'cancel', enabled: true },
];
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>()(
persist(
(set) => ({
enabled: true,
shortcuts: defaultShortcuts,
setEnabled: (enabled) => set({ enabled }),
updateShortcut: (key, updates) =>
set((state) => ({
shortcuts: state.shortcuts.map((s) =>
s.key === key ? { ...s, ...updates } : s
),
})),
resetShortcuts: () => set({ shortcuts: defaultShortcuts }),
}),
{
name: 'project-e-keyboard-shortcuts',
}
)
);
@@ -0,0 +1,28 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface SidebarState {
collapsed: boolean;
mobileOpen: boolean;
// Actions
toggle: () => void;
setCollapsed: (collapsed: boolean) => void;
setMobileOpen: (open: boolean) => void;
}
export const useSidebarStore = create<SidebarState>()(
persist(
(set) => ({
collapsed: false,
mobileOpen: false,
toggle: () => set((state) => ({ collapsed: !state.collapsed })),
setCollapsed: (collapsed) => set({ collapsed }),
setMobileOpen: (mobileOpen) => set({ mobileOpen }),
}),
{
name: 'project-e-sidebar',
}
)
);
@@ -0,0 +1,41 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type ThemeMode = 'light' | 'dark' | 'system';
type Density = 'compact' | 'comfortable' | 'spacious';
interface ThemeState {
mode: ThemeMode;
accent: string;
font: string;
density: Density;
reducedMotion: boolean;
// Actions
setMode: (mode: ThemeMode) => void;
setAccent: (color: string) => void;
setFont: (font: string) => void;
setDensity: (density: Density) => void;
setReducedMotion: (enabled: boolean) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
mode: 'system',
accent: '#356bff',
font: 'geist',
density: 'comfortable',
reducedMotion: false,
setMode: (mode) => set({ mode }),
setAccent: (accent) => set({ accent }),
setFont: (font) => set({ font }),
setDensity: (density) => set({ density }),
setReducedMotion: (reducedMotion) => set({ reducedMotion }),
}),
{
name: 'project-e-theme',
}
)
);
@@ -0,0 +1,71 @@
import { create } from 'zustand';
type TimerMode = 'focus' | 'break' | 'idle';
interface TimerState {
mode: TimerMode;
isRunning: boolean;
secondsRemaining: number;
focusMinutes: number;
breakMinutes: number;
currentTaskId: string | null;
// Actions
startFocus: (taskId?: string) => void;
startBreak: () => void;
pause: () => void;
resume: () => void;
reset: () => void;
tick: () => void;
setFocusMinutes: (minutes: number) => void;
setBreakMinutes: (minutes: number) => void;
}
export const useTimerStore = create<TimerState>()((set, get) => ({
mode: 'idle',
isRunning: false,
secondsRemaining: 0,
focusMinutes: 25,
breakMinutes: 5,
currentTaskId: null,
startFocus: (taskId) =>
set({
mode: 'focus',
isRunning: true,
secondsRemaining: get().focusMinutes * 60,
currentTaskId: taskId ?? null,
}),
startBreak: () =>
set({
mode: 'break',
isRunning: true,
secondsRemaining: get().breakMinutes * 60,
}),
pause: () => set({ isRunning: false }),
resume: () => set({ isRunning: true }),
reset: () =>
set({
mode: 'idle',
isRunning: false,
secondsRemaining: 0,
currentTaskId: null,
}),
tick: () => {
const state = get();
if (!state.isRunning || state.secondsRemaining <= 0) {
if (state.secondsRemaining <= 0) {
set({ isRunning: false, mode: 'idle' });
}
return;
}
set({ secondsRemaining: state.secondsRemaining - 1 });
},
setFocusMinutes: (focusMinutes) => set({ focusMinutes }),
setBreakMinutes: (breakMinutes) => set({ breakMinutes }),
}));