Target Architecture Documentation
Version: 1.0.0
Status: PROPOSED
Date: 2025-01-29
Purpose: Defines the target architecture to solve current problems
Target System Overview
Sasha Studio will implement targeted, granular updates where file changes trigger minimal UI updates based on change type and scope.
Target Architecture Diagram
User Input β UI β WebSocket β Server β Claude CLI β File System
β
JSONL Session Files
β
Smart File Watcher
β
Analyze Change Type & Scope
β
Route to Appropriate Handler
/ | \
Documentation Message Stream File Change
β β β
HTML Generation Stream Parser Tree Update
β β β
Toast Message Single Message Minimal Update
β β β
No Re-render Append Only Tree Only
Key Architectural Changes
1. Targeted WebSocket Messages
Instead of: Sending entire projects array
Send: Specific update messages
// Documentation Update
{
type: 'documentation_updated',
projectId: 'project-123',
file: 'docs/guide.md',
htmlPath: '/html-static/content/guide.html',
operation: 'created|modified|deleted'
}
// Message Stream Update (RECOMMENDED)
{
type: 'message_streamed',
projectId: 'project-123',
sessionId: 'session-456',
message: {
id: 'msg-123',
type: 'assistant',
content: '...',
timestamp: '...'
},
operation: 'append|replace|delete'
}
// Alternative: Message Delta Update
{
type: 'messages_appended',
projectId: 'project-123',
sessionId: 'session-456',
newMessages: [...], // Only new messages
startIndex: 47 // Where to insert
}
// File Change
{
type: 'project_files_changed',
projectId: 'project-123',
changes: [
{ path: 'src/app.js', operation: 'add|modify|delete' }
]
}
2. Smart File Watcher with Message Streaming
Location: server/services/smart-watcher.js (new)
class SmartWatcher {
constructor() {
this.watchers = new Map();
this.lastMessagePosition = new Map(); // Track file positions for streaming
this.debounces = {
documentation: 1000, // 1s for docs
session: 50, // 50ms for streaming messages
files: 500 // 500ms for code
};
}
async routeFileChange(path, changeType) {
if (path.includes('/docs/') && path.endsWith('.md')) {
return { handler: 'documentation', debounce: 1000 };
}
if (path.includes('/sessions/') && path.endsWith('.jsonl')) {
// For session files, read only new lines (streaming)
const lastPos = this.lastMessagePosition.get(path) || 0;
const newMessages = await this.readNewLines(path, lastPos);
return {
handler: 'message_stream',
debounce: 50, // Very short for real-time feel
messages: newMessages
};
}
return { handler: 'files', debounce: 500 };
}
async readNewLines(filePath, fromPosition) {
// Read only new lines added to JSONL file
const stream = fs.createReadStream(filePath, { start: fromPosition });
const newMessages = [];
// Parse and return only new messages
return newMessages;
}
}
3. State Machine for Operations
Replace: Multiple boolean flags
With: Single state machine
// New state structure
{
operationState: {
current: 'idle|selecting_project|creating_session|loading_files',
context: {
projectId: 'project-123',
sessionId: 'session-456',
startTime: 1706527200000
}
},
projects: [],
selectedIds: {
project: 'project-123',
session: 'session-456'
},
messageStreams: {
'session-456': {
messages: [], // Append-only array
lastMessageId: 'msg-123',
isStreaming: false
}
},
cache: {
files: { 'project-123': [...] },
lastUpdate: { 'project-123': 1706527200000 }
}
}
4. URL Routing Structure
New Route Pattern:
/project/{projectName}/chat/{sessionId}
/project/{projectName}/files/{path}
/project/{projectName}/docs/{docPath}
/project/{projectName}/settings
Implementation:
<Routes>
<Route path="/" element={<Welcome />} />
<Route path="/project/:projectName" element={<ProjectLayout />}>
<Route index element={<Navigate to="chat" />} />
<Route path="chat" element={<ChatView />} />
<Route path="chat/:sessionId" element={<ChatView />} />
<Route path="files/*" element={<FilesView />} />
<Route path="docs/*" element={<DocsView />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
5. Optimized Reducer Actions
New Action Types:
const ActionTypes = {
// State machine transitions
SET_OPERATION_STATE: 'SET_OPERATION_STATE',
// Message streaming (RECOMMENDED)
APPEND_MESSAGE: 'APPEND_MESSAGE',
UPDATE_MESSAGE: 'UPDATE_MESSAGE',
// Targeted updates
UPDATE_PROJECT_FILES: 'UPDATE_PROJECT_FILES',
SHOW_DOCUMENTATION_TOAST: 'SHOW_DOCUMENTATION_TOAST',
// Atomic operations
SELECT_PROJECT_AND_SESSION: 'SELECT_PROJECT_AND_SESSION',
CREATE_AND_SELECT_SESSION: 'CREATE_AND_SELECT_SESSION'
};
Message Handling Pattern:
case 'APPEND_MESSAGE':
// Stream single message - no array replacement
return {
...state,
messageStreams: {
...state.messageStreams,
[action.sessionId]: {
...state.messageStreams[action.sessionId],
messages: [
...(state.messageStreams[action.sessionId]?.messages || []),
action.message
],
lastMessageId: action.message.id,
isStreaming: action.message.isStreaming || false,
lastUpdate: Date.now()
}
}
};
case 'UPDATE_MESSAGE':
// Update streaming message content
const stream = state.messageStreams[action.sessionId];
const messageIndex = stream.messages.findIndex(m => m.id === action.messageId);
if (messageIndex === -1) return state;
const updatedMessages = [...stream.messages];
updatedMessages[messageIndex] = {
...updatedMessages[messageIndex],
...action.updates,
isStreaming: action.isStreaming
};
return {
...state,
messageStreams: {
...state.messageStreams,
[action.sessionId]: {
...stream,
messages: updatedMessages,
isStreaming: action.isStreaming
}
}
};
6. Component Optimization
App.jsx Refactor:
// Split mega useEffect into focused effects
useEffect(() => {
// Only handle URL synchronization
}, [selectedIds.project, selectedIds.session]);
useEffect(() => {
// Only handle initial data loading
}, [selectedIds.project]);
ChatInterface.jsx Refactor:
// Replace ref dance with state machine
const [onboardingState, dispatch] = useReducer(onboardingReducer, {
phase: 'idle', // idle β detected β submitting β complete
prompt: null,
error: null
});
7. Storage Strategy
Clear Separation:
| Data Type | Storage Location | Lifecycle |
|---|---|---|
| User preferences | localStorage | Persistent |
| Auth token | localStorage | Until logout |
| Onboarding prompt | sessionStorage | One-time use |
| Project state | Reducer only | Runtime |
| Session messages | JSONL files only | Persistent |
| File cache | Reducer cache | Runtime |
Remove From Storage:
- All
sasha_chat_messages_*from localStorage pendingSessionId,activeSessionIdfrom sessionStorage- Any duplicated state
8. Performance Targets
| Metric | Current | Target | How |
|---|---|---|---|
| Re-renders per message | 10-20 | 1-2 | Message streaming |
| WebSocket message size | ~100KB | <500 bytes | Single message only |
| Message display latency | 800ms | <100ms | Direct streaming |
| Project switches | 3-5 | 1 | Atomic operations |
| File fetches per project | 10-40 | 1 | Proper caching |
| Debounce delay | 300ms | 50ms for messages | Type-specific |
9. Stable Dependencies
useEffect Rules:
// β
GOOD - Stable primitives
useEffect(() => {
loadProject(projectId);
}, [projectId]); // String is stable
// β BAD - Unstable objects
useEffect(() => {
processProjects(projects);
}, [projects]); // Array recreated every render
10. Event Flow Optimization
1. File Change Detection
ββ Identify type (doc/session/file)
2. Type-Specific Processing
ββ Documentation β Generate HTML β Toast only
ββ Session β Parse new message β Stream single message
ββ Files β Update tree β Refresh file list only
3. Targeted WebSocket Message
ββ Send only relevant data (single message, not array)
4. Minimal Client Update
ββ Append message to existing array (no replacement)
5. Surgical State Change
ββ Preserve unchanged references
Implementation Priority
Phase 1: Foundation (Week 1)
- Implement state machine for operations
- Clean up storage (remove duplicates)
- Fix useEffect dependencies
- Add message streaming structure to state
Phase 2: Message Streaming (Week 2) - CRITICAL
- Implement JSONL incremental reader
- Create message streaming WebSocket handler
- Update reducer for single message append
- Test with real-time Claude responses
Phase 3: Smart Watcher & Routing (Week 3)
- Create smart file watcher with stream detection
- Implement new URL route structure
- Add deep linking support
- Fix browser navigation
Phase 4: Optimization (Week 4)
- Add React.memo to message components
- Implement virtual scrolling for long chats
- Performance testing with 1000+ messages
Success Metrics
User Experience
- Zero UI flashing during updates
- Instant response to user actions
- Working browser navigation
- Shareable URLs
Technical Performance
- 3x reduction in re-renders
- 20x reduction in WebSocket payload
- 10x reduction in unnecessary file fetches
- Sub-100ms UI updates
Code Quality
- Single source of truth for state
- Clear separation of concerns
- Testable architecture
- Maintainable code structure
Risk Mitigation
Risk 1: Breaking Changes
Mitigation: Implement changes behind feature flags
Risk 2: WebSocket Compatibility
Mitigation: Support both old and new message formats during transition
Risk 3: Performance Regression
Mitigation: Add performance monitoring before changes
Validation Approach
- Unit Tests: Test each handler independently
- Integration Tests: Test full update flow
- Performance Tests: Measure re-render counts
- User Testing: Validate UX improvements
This architecture will transform Sasha Studio from a system that re-renders everything on every change to one that surgically updates only what's necessary.