# Cocoanut Architecture
## Overview
```
┌─────────────────────────────────────────┐
│ User Code (Rust) │
│ │
│ app("My App") │
│ .build() │
│ .root(View::vstack() │
│ .child(View::text("Hello")) │
│ .child(View::button("Click"))) │
│ .run() │
├─────────────────────────────────────────┤
│ lib.rs → exports & prelude │
│ view.rs → View + ViewKind enum │
│ app.rs → App + Appearance lifecycle │
│ renderer.rs → View tree → AppKit │
│ event.rs → Callback registry + ObjC │
│ state.rs → Reactive State<T> │
│ menu.rs → Menu + MenuItem │
│ error.rs → CocoanutError + Result │
├─────────────────────────────────────────┤
│ AppKit (macOS) │
│ NSApplication, NSWindow, NSButton, ... │
└─────────────────────────────────────────┘
```
## Design Principles
Learned from [swiftui-rs](../swiftui-rs):
- **KISS** — One `View` type, one `ViewKind` enum. Not 23 separate structs.
- **DRY** — Single renderer maps all view kinds to AppKit. No duplication.
- **SoC** — `view.rs` defines *what* to show. `renderer.rs` decides *how*.
## Components
### `view.rs` — The Core Type
Everything is a `View`:
```rust
pub struct View {
kind: ViewKind, // What kind of view
children: Vec<View>, // Composable children
style: ViewStyle, // Visual properties
event: Option<EventBinding>, // Interaction
}
```
`ViewKind` is a single enum with all 26 view types (Text, Button, VStack, Slider, DatePicker, etc.).
Views are created with static constructors and composed with `.child()`:
```rust
View::vstack()
.child(View::text("Hello").bold())
.child(View::button("OK").on_click(1))
```
### `renderer.rs` — Single Rendering Pass
One function walks the view tree and creates AppKit NSViews:
```rust
pub fn render(root: &View, content_view: *mut Object, bounds: NSRect) -> Result<()>
```
Each `ViewKind` maps to exactly one AppKit class. Layout containers (VStack, HStack) calculate child positions and recurse.
### `app.rs` — Lifecycle
Builder pattern for app creation:
```rust
app("Title").size(800.0, 600.0).build().root(view_tree).run()
```
`run()` initializes NSApplication, creates NSWindow, calls `renderer::render()`, and starts the event loop.
### `event.rs` — Callback System
Global callback registry + custom ObjC class:
```rust
```
### `state.rs` — Reactive State
```rust
let count = counter_state(1, 2, 3); // auto-wires +1/-1/reset callbacks
count.on_change(|v| println!("Count: {}", v));
bind_label(&count, 100, |v| format!("Count: {}", v)); // updates NSTextField with tag 100
// In the view tree: View::label("Count: 0").tag(100)
```
### `menu.rs` — Menu System
Declarative menu construction with action dispatch:
```rust
Menu::new("File")
.item(MenuItem::new("Open").shortcut("o").on_action(100))
.separator()
.item(MenuItem::new("Quit").shortcut("q"))
```
### `error.rs` — Error Handling
Single `CocoanutError` enum with `thiserror` derive. `Result<T>` type alias.
## Data Flow
```
View::vstack().child(View::text("Hi"))
→ View { kind: VStack, children: [View { kind: Text("Hi") }] }
→ renderer::render() walks tree recursively
→ VStack calculates child bounds, recurses
→ Text creates NSTextField, adds to parent NSView
→ NSApplication.run() starts event loop
```
## Key Decisions
- **View tree over individual component structs** — simpler, composable, no duplication
- **Single renderer** — all AppKit mapping in one file, easy to maintain
- **ObjC target/action for events** — one custom class dispatches all control actions to Rust closures
- **Reactive State<T>** — generic state container with listener pattern, no full re-render needed
- **Dark mode via NSAppearance** — per-window appearance, switchable at runtime
- **JSON serializable** — `ViewDesc` enables debugging and potential bridging
- **test-mock feature** — renderer validates tree structure without AppKit in tests
- **Minimal deps** — only objc, cocoa, thiserror, serde, serde_json
## File Summary
| `lib.rs` | ~45 | Exports, prelude |
| `view.rs` | ~600 | View, ViewKind (26 types), ViewStyle, ViewDesc |
| `renderer.rs` | ~600 | View tree → AppKit NSViews |
| `app.rs` | ~220 | App lifecycle, Appearance, dark mode |
| `event.rs` | ~150 | Callback registry, ObjC action handler |
| `state.rs` | ~180 | Reactive State<T>, bind_label |
| `menu.rs` | ~160 | Menu bar with action dispatch |
| `error.rs` | ~65 | Error types |
| **Total** | **~2020** | |
Previous version had 114 files / 26,878 lines. This is a **92% reduction**.