The common-ui package implements a four-layer architecture for maximum code reuse and maintainability. This architecture strictly separates business logic, coordination, UI contracts, and platform-specific implementations.
Purpose: Platform-independent business logic and state management
- ✅ Pure functions, no UI dependencies
- ✅ Singleton export:
export const service = new Service() - ✅ All calculations, transformations, and validations
- ✅ Comprehensive error handling
Example:
export class ButtonStateService {
calculateButtonState(state: SimulatorState): MainButtonState {
// Pure business logic
}
}
export const buttonStateService = new ButtonStateService();Purpose: Coordination between services and UI widgets
- ✅ NEVER implements View interfaces (critical anti-pattern!)
- ✅ Delegates all business logic to services
- ✅ Event dispatching and routing
- ✅ Singleton export:
export const controller = new Controller()
Example:
export class DebuggerController {
update(memory: Memory, simulator: Simulator) {
// Delegate to service
debuggerStateService.updateFromSimulatorState(simulator.state);
// Update UI
this.events.dispatch("stateChanged", newState);
}
}Purpose: Abstract UI contracts for platform-specific implementations
- ✅ Only UI-specific method signatures
- ✅ No implementations or business logic
- ✅ Clear, minimal contracts
Example:
export interface DebuggerView {
update(memory: Memory): void;
log(message: string): void;
}Purpose: Concrete UI implementations
- ✅ Implements interface contracts
- ✅ Uses controllers for state management
- ✅ Platform-specific UI frameworks (GTK, React, etc.)
| Service | Purpose | Platform |
|---|---|---|
button-state-service |
Button state calculations | shared |
debugger-state-service |
Debugger state management | shared |
game-console-state-service |
Game console rendering | shared |
game-console-input-service |
Gamepad/keyboard handling | shared |
learn-state-service |
Learning progress management | shared |
file-service |
File operations | platform-specific |
theme-service |
Theme management | platform-specific |
notification-service |
Notifications | platform-specific |
- Always export singleton instances
- Keep state minimal and focused
- Use pure functions where possible
- Document all public methods
- NEVER implement View interfaces (anti-pattern!)
- Delegate all business logic to services
- Use EventDispatcher for loose coupling
- Keep methods small and focused
- Define only UI-specific operations
- Keep contracts minimal but complete
- Use clear, descriptive names
- ❌
class Controller implements ViewInterface - ❌ Business logic in controllers
- ❌ UI dependencies in services
- ❌ Mixed responsibilities
- Extract business logic → create new service
- Clean up controller → remove
implements ViewInterface - Implement delegation → controller calls service methods
- Update interfaces → keep only UI methods
- Test platforms → ensure all implementations still work
// ❌ OLD (Anti-Pattern)
class OldController implements ViewInterface {
update(data: Input) {
// Business logic mixed with UI
const result = this.calculateState(data);
this.updateUI(result);
}
}
// ✅ NEW (Four-Layer Architecture)
class StateService {
calculateState(data: Input): Output {
// Pure business logic
}
}
class NewController {
update(data: Input) {
// Delegate to service
const result = stateService.calculateState(data);
// Dispatch to UI
this.events.dispatch("updated", result);
}
}- 🔄 Maximum Reuse - Services run on all platforms
- 🧪 Easy to Test - Pure services without UI dependencies
- 🛠️ Maintainable - Clear separation of responsibilities
- 📱 Platform Independent - Services are framework-agnostic
- 🎯 Focused - Each layer has a single responsibility