nexus/.planning/STATE.md

14 KiB

gsd_state_version milestone milestone_name status stopped_at last_updated last_activity progress
1.0 v1.3 milestone executing Completed 26-04-PLAN.md 2026-04-02T10:49:24.152Z 2026-04-02
total_phases completed_phases total_plans completed_plans percent
6 6 35 35 100

Project State

Project Reference

See: .planning/PROJECT.md (updated 2026-03-30)

Core value: Fresh onboard asks for ONE thing (root directory), auto-creates PM + Engineer, drops you in dashboard — no corporate language anywhere. Current focus: Phase 26 — pwa-performance

Current Position

Phase: 26 Plan: Not started Status: Ready to execute Last activity: 2026-04-02

Progress: [██████████] 100%

Upstream Rebase Log

Date Commits Behind Conflicts Build Notes
2026-04-01 0 0 OK Already rebased from 120+ commits session; upstream hasn't moved

Performance Metrics

Velocity:

  • Total plans completed: 0
  • Average duration: -
  • Total execution time: 0 hours

By Phase:

Phase Plans Total Avg/Plan
- - - -

Recent Trend:

  • Last 5 plans: none yet
  • Trend: -

Updated after each plan completion | Phase 01-foundation P01 | 2 | 2 tasks | 7 files | | Phase 21-chat-foundation P02 | ~15min | 2 tasks | 5 files | | Phase 21-chat-foundation P01 | 2 | 2 tasks | 8 files | | Phase 21-chat-foundation P00 | 2 | 2 tasks | 4 files | | Phase 21-chat-foundation P04 | 4min | 2 tasks | 7 files | | Phase 21-chat-foundation P03 | 6 | 2 tasks | 6 files | | Phase 21-chat-foundation P05 | 4 | 3 tasks | 8 files | | Phase 21-chat-foundation P06 | 10min | 2 tasks | 7 files | | Phase 22-agent-streaming P01 | 6min | 2 tasks | 6 files | | Phase 22-agent-streaming P03 | 3 | 2 tasks | 4 files | | Phase 22-agent-streaming P04 | 4min | 2 tasks | 5 files | | Phase 22-agent-streaming P05 | 20min | 3 tasks | 6 files | | Phase 23-brainstormer-flow P00 | 3min | 2 tasks | 11 files | | Phase 23-brainstormer-flow P02 | 5min | 2 tasks | 6 files | | Phase 23-brainstormer-flow P01 | 10min | 2 tasks | 2 files | | Phase 23-brainstormer-flow P03 | 5 | 2 tasks | 4 files | | Phase 24-search-history-branching P00 | 5 | 2 tasks | 9 files | | Phase 24-search-history-branching P01 | 12 | 2 tasks | 2 files | | Phase 24-search-history-branching P02 | 3min | 2 tasks | 7 files | | Phase 24-search-history-branching P03 | 4 | 3 tasks | 7 files | | Phase 25-file-system P00 | 6 | 2 tasks | 11 files | | Phase 25-file-system P02 | 15 | 2 tasks | 5 files | | Phase 25-file-system P01 | 15 | 2 tasks | 17 files | | Phase 25-file-system P03 | 3 | 2 tasks | 7 files | | Phase 25-file-system P08 | 8 | 2 tasks | 5 files | | Phase 25-file-system P04 | 5min | 2 tasks | 5 files | | Phase 25-file-system P06 | 5 | 2 tasks | 5 files | | Phase 25-file-system P07 | 10 | 2 tasks | 6 files | | Phase 26-pwa-performance P00 | 5 | 2 tasks | 9 files | | Phase 26-pwa-performance P01 | 4 | 2 tasks | 2 files | | Phase 26-pwa-performance P02 | 20 | 2 tasks | 8 files | | Phase 26-pwa-performance P04 | 15 | 2 tasks | 10 files |

Accumulated Context

Decisions

Decisions are logged in PROJECT.md Key Decisions table. Recent decisions affecting current work:

  • Roadmap: Company → Workspace (not Project) to avoid collision with existing Project entity
  • Roadmap: Display-only renames — all code identifiers, DB schema, routes, env vars unchanged
  • Roadmap: Branding package (packages/branding/) as single string mutation surface
  • Roadmap: Vite alias redirects OnboardingWizard import to Nexus-owned replacement (Phase 4)
  • Roadmap: ~/.nexus pointer file with read-both-paths fallback for migration safety (Phase 2)
  • [Phase 01-foundation]: Keep @paperclipai/branding package name for upstream sync compatibility
  • [Phase 01-foundation]: Use as const for VOCAB to enable TypeScript literal type inference on all values
  • [Phase 01-foundation]: Hook source tracked in scripts/nexus-commit-msg-hook.sh for post-clone reinstallation
  • [Phase 01-foundation]: rerere.autoupdate=true so resolved conflicts are auto-staged during future rebases
  • [Phase 21-chat-foundation]: Used object-syntax (table) => ({}) for Drizzle index callbacks to match existing codebase convention in documents.ts, agents.ts
  • [Phase 21-chat-foundation]: Use it.todo() (not it.skip()) for Wave 0 test scaffolding — vitest marks todos semantically, no false positives
  • [Phase 21-chat-foundation]: Minimal imports in test stubs — no service mocks until Plans 01-05 wire up implementations
  • [Phase 21-02]: Use ExtraProps from react-markdown for ChatCodeBlock type signature to satisfy ComponentType constraint
  • [Phase 21-02]: Add hljs CSS as plain rules (not @import) scoped to .dark, .theme-tokyo-night, :root selectors
  • [Phase 21-chat-foundation]: ChatPanelProvider inside PanelProvider so ChatPanel can call setPanelVisible to close PropertiesPanel
  • [Phase 21-chat-foundation]: Chat toggle button in desktop sidebar bottom controls with hidden md:inline-flex
  • [Phase 21-03]: Pitfall 3 (updatedAt bump): addMessage always updates chatConversations.updatedAt after inserting — ensures conversation list sort order stays correct
  • [Phase 21-03]: Pitfall 5 (auto-title idempotency): WHERE title IS NULL guard makes auto-title set safe to call multiple times
  • [Phase 21-03]: Missing export fix: createConversationSchema/updateConversationSchema/createMessageSchema were in validators/chat.ts but not re-exported from shared/src/index.ts
  • [Phase 21-chat-foundation]: Two-path handleSend in ChatPanel: direct chatApi for path 1 (no active conversation) avoids hook mutation needing a conversationId that does not exist yet
  • [Phase 21-chat-foundation]: messages array in useChatMessages flattened from pages and reversed so display is chronological (API returns desc by createdAt)
  • [Phase 21-chat-foundation]: Custom window event (nexus:focus-chat-search) used instead of forwardRef drilling to focus search input from Cmd+K
  • [Phase 21-chat-foundation]: Cmd+K handler placed before input-guard early return in useKeyboardShortcuts so it fires globally even from input/textarea
  • [Phase 22-agent-streaming]: Use fetch ReadableStream instead of EventSource for POST SSE streaming endpoint
  • [Phase 22-agent-streaming]: streamEcho stub yields word-by-word with 50ms delay; Phase 23 replaces with real LLM adapter
  • [Phase 22-agent-streaming]: Partial content on stop saved with [stopped] suffix via chatApi.savePartialMessage
  • [Phase 22-agent-streaming]: isAnyStreaming prop (not isStreaming) distinguishes global streaming state for disabling edit/retry globally in ChatMessage
  • [Phase 22-agent-streaming]: /search disabled with Coming soon; resolveAgentFromContent routes slash > @mention > active agent
  • [Phase 22-agent-streaming]: [Phase 22-05]: virtualizer.measure() on streamingContent change handles dynamic height re-measurement for growing streaming message (Pitfall 3)
  • [Phase 22-agent-streaming]: [Phase 22-05]: handleRetry truncates from user message (not assistant), removing assistant + all subsequent messages before re-streaming
  • [Phase 23-brainstormer-flow]: Added journal entries for idx 47 (nebulous_klaw) and 48 (add_chat_messages_updated_at) retroactively to keep journal consistent with files on disk
  • [Phase 23-brainstormer-flow]: Used @/lib/router Link abstraction (not react-router-dom) for ChatTaskCreatedBadge and ChatStatusUpdateBadge — consistent with all other link components in the codebase
  • [Phase 23-brainstormer-flow]: ChatSpecCardInner extracted as inner component to avoid conditional hook calls after JSON.parse error path in ChatSpecCard
  • [Phase 23-brainstormer-flow]: issueService imported directly in chatRoutes(db) — option (a) from plan, simplest approach matching heartbeat.ts pattern
  • [Phase 23-brainstormer-flow]: Used activeConversationId === null as proxy for new conversation in brainstormer auto-select
  • [Phase 23-brainstormer-flow]: Used useToast()/pushToast() for error toast in ChatPanel handleHandoff (custom ToastContext, not sonner)
  • [Phase 24-search-history-branching]: Used AnyPgColumn type annotation for parentConversationId self-referential FK — matches existing pattern in issues.ts, goals.ts, execution_workspaces.ts
  • [Phase 24-search-history-branching]: content_search tsvector column omitted from Drizzle schema — Postgres-generated stored column queried via raw sql`` only
  • [Phase 24-search-history-branching]: searchMessages returns empty items early when query.trim() is empty — avoids PostgreSQL error on blank tsquery
  • [Phase 24-search-history-branching]: exportConversation uses this.getConversation() to reuse notFound guard without duplicating query logic
  • [Phase 24-search-history-branching]: Used HighlightedText React component instead of innerHTML for term highlighting — eliminates XSS surface
  • [Phase 24-search-history-branching]: exportConversation returns URL string (not fetch call) since server sends file download via browser navigation
  • [Phase 24-search-history-branching]: shouldFilter=false pattern on Command for server-side FTS — all future search dialogs should follow this
  • [Phase 24-search-history-branching]: Custom event nexus:open-chat-search routes search trigger through CommandPalette without Cmd+K conflict
  • [Phase 24-search-history-branching]: Branch-on-edit checks editedIdx < messages.length - 1 before calling branchConversation
  • [Phase 24-search-history-branching]: bookmarkedMessageIds as Set for O(1) lookup rebuilt from useChatBookmarks per-conversation data
  • [Phase 25-file-system]: Used object-syntax (table) => ({}) for Drizzle index callbacks in chat_files and chat_file_references — matches existing codebase pattern
  • [Phase 25-file-system]: chatFiles uses nullable FKs (SET NULL) for conversationId/messageId — file may exist before a conversation or message is created
  • [Phase 25-file-system]: chatFileReferences uses CASCADE deletes for fileId and conversationId — reference has no meaning without both anchors
  • [Phase 25-file-system]: Used XHR instead of fetch for chatApi.uploadFile to enable upload progress events (fetch lacks upload.onprogress)
  • [Phase 25-file-system]: ChatInput onFilesPicked/pendingFiles/onRemoveFile props are all optional for backward compatibility
  • [Phase 25-file-system]: Content-Length uses object.contentLength from storage ?? chatFile.sizeBytes to prevent stream ECONNRESET
  • [Phase 25-file-system]: createFileReferenceSchema.safeParse() receives fileId injected from URL param for UUID format validation
  • [Phase 25-file-system]: ChatFilePreview shows inline image with max-h-[300px] + ChatFileCard below; non-image types use ChatFileCard only
  • [Phase 25-file-system]: listMessages fetches chatFiles with inArray(messageId) as second query, merged in-memory
  • [Phase 25-file-system]: completedFileIds captured before clearCompleted in handleSend to avoid race condition
  • [Phase 25-file-system]: Use functional setState for transcription append in VoiceRecordButton — avoids stale closure vs native DOM event approach
  • [Phase 25-file-system]: enableVoiceInput defaults to false for backward-compat; ChatPanel passes true unconditionally — server returns 503 gracefully if whisper absent
  • [Phase 25-file-system]: execFileAsync over exec for whisper CLI invocation — no shell injection risk with system-generated tmpPath
  • [Phase 25-file-system]: Used DOMParser + replaceChildren to safely render hljs output — avoids raw HTML injection pattern while preserving same visual output as rehype-highlight
  • [Phase 25-file-system]: highlight.js added as explicit ui/package.json dependency (was transitive via rehype-highlight only)
  • [Phase 25-file-system]: Used execFile (not exec) for git commands in gitFileService — array-based args prevent shell injection
  • [Phase 25-file-system]: Git commit is fire-and-forget after upload — response not blocked by git operation
  • [Phase 25-file-system]: History route placed before /content route to avoid Express path ambiguity on /files/:fileId/*
  • [Phase 25-file-system]: placeholderService reads/writes PLACEHOLDERS.md using regex row parser to avoid markdown deps
  • [Phase 25-file-system]: addEntry is fire-and-forget — response not blocked by placeholder manifest I/O
  • [Phase 25-file-system]: resolveDefaultStorageDir() used for projectDir in placeholder service — consistent with git-file-service pattern
  • [Phase 26-pwa-performance]: [Phase 26-00]: Cache name changed from paperclip-v2 to nexus-v1; activate event deletes all non-nexus-v1 caches to bust stale entries
  • [Phase 26-pwa-performance]: [Phase 26-00]: API paths (/api/*) pass through without SW interception — network-only for all API traffic
  • [Phase 26-pwa-performance]: All pages use named exports so React.lazy requires .then(m => ({ default: m.X })) module re-mapping
  • [Phase 26-pwa-performance]: manualChunks: vendor-react, vendor-router, vendor-query, vendor-markdown (excludes @mdxeditor/editor to avoid circular deps)
  • [Phase 26-pwa-performance]: useMediaQuery uses addEventListener('change') not addListener() — addListener is deprecated in modern browsers
  • [Phase 26-pwa-performance]: PullToRefresh wraps ScrollArea in ChatConversationList, not the entire list component — keeps desktop layout unaffected
  • [Phase 26-pwa-performance]: MobileChatView uses 100dvh not 100vh — avoids keyboard-shrink issue (RESEARCH Pitfall 3)
  • [Phase 26-pwa-performance]: pushService uses named exports (not class) matching existing chat.ts service pattern
  • [Phase 26-pwa-performance]: initVapid is graceful — checks env vars before calling setVapidDetails, logs warning if absent
  • [Phase 26-pwa-performance]: sendPushToAll uses Promise.allSettled so one failed delivery doesn't block others; stale 410/404 subscriptions auto-deleted
  • [Phase 26-pwa-performance]: DELETE /api/push/subscribe uses request body (not URL param) — endpoints are long URLs; api.delete() extended with direct fetch
  • [Phase 26-pwa-performance]: NotificationPermissionPrompt engagement gate: agentResponseCount >= 3 derived via useMemo from messages with role === assistant

Pending Todos

None yet.

Blockers/Concerns

  • Phase 4: POST /api/companies required fields not fully documented — read server/src/routes/companies.ts before implementing new wizard
  • Phase 3: Exact count of test files asserting on old display strings unknown — grep audit needed as first step

Session Continuity

Last session: 2026-04-02T02:33:59.681Z Stopped at: Completed 26-04-PLAN.md Resume file: None