Project 9: Pane Splitting and Layouts
Build a layout tree that supports vertical and horizontal splits with resize logic.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 4: Expert |
| Time Estimate | 2-3 weeks |
| Main Programming Language | C (Alternatives: Rust) |
| Alternative Programming Languages | Rust |
| Coolness Level | Level 4: Geometry Hacker |
| Business Potential | 2: The “Window Manager” |
| Prerequisites | trees, screen composition |
| Key Topics | layout trees, split ratios, resize propagation |
1. Learning Objectives
By completing this project, you will:
- Build a working implementation of pane splitting and layouts and verify it with deterministic outputs.
- Explain the underlying Unix and terminal primitives involved in the project.
- Diagnose common failure modes with logs and targeted tests.
- Extend the project with performance and usability improvements.
2. All Theory Needed (Per-Concept Breakdown)
Layout Trees and Resize Propagation
-
Fundamentals Layout Trees and Resize Propagation is the core contract that makes the project behave like a real terminal tool. It sits at the boundary between raw bytes and structured state, so you must treat it as both a protocol and a data model. The goal of the fundamentals is to understand what assumptions the system makes about ordering, buffering, and ownership, and how those assumptions surface as user-visible behavior. Key terms include: split, ratio, leaf, min size. In practice, the fastest way to gain intuition is to trace a single input through the pipeline and note where it can be delayed, reordered, or transformed. That exercise reveals why Layout Trees and Resize Propagation needs explicit invariants and why even small mistakes can cascade into broken rendering or stuck input.
-
Deep Dive into the concept A deep understanding of Layout Trees and Resize Propagation requires thinking in terms of state transitions and invariants. You are not just implementing functions; you are enforcing a contract between producers and consumers of bytes, and that contract persists across time. Most failures in this area are caused by violating ordering guarantees, dropping state updates, or misunderstanding how the operating system delivers events. This concept is built from the following pillars: split, ratio, leaf, min size. A reliable implementation follows a deterministic flow: Represent splits as binary nodes -> Compute child rectangles -> Clamp to minimum sizes -> Propagate resize to leaves. From a systems perspective, the tricky part is coordinating concurrency without introducing races. Even in a single-threaded loop, multiple events can arrive in the same tick, so you need deterministic ordering. This is why many implementations keep a strict sequence: read, update state, compute diff, render. Another subtlety is error handling and recovery. A robust design treats errors as part of the normal control flow: EOF is expected, partial reads are expected, and transient failures must be retried or gracefully handled. The deep dive should also cover how to observe the system, because without logs and trace points, you cannot reason about correctness. When you design the project, treat each key term as a source of constraints. For example, if a term implies buffering, decide the buffer size and how overflow is handled. If a term implies state, decide how that state is initialized, updated, and reset. Finally, validate your assumptions with deterministic fixtures so you can reproduce bugs. From a systems perspective, the tricky part is coordinating concurrency without introducing races. Even in a single-threaded loop, multiple events can arrive in the same tick, so you need deterministic ordering. This is why many implementations keep a strict sequence: read, update state, compute diff, render. Another subtlety is error handling and recovery. A robust design treats errors as part of the normal control flow: EOF is expected, partial reads are expected, and transient failures must be retried or gracefully handled. The deep dive should also cover how to observe the system, because without logs and trace points, you cannot reason about correctness. From a systems perspective, the tricky part is coordinating concurrency without introducing races. Even in a single-threaded loop, multiple events can arrive in the same tick, so you need deterministic ordering. This is why many implementations keep a strict sequence: read, update state, compute diff, render. Another subtlety is error handling and recovery. A robust design treats errors as part of the normal control flow: EOF is expected, partial reads are expected, and transient failures must be retried or gracefully handled. The deep dive should also cover how to observe the system, because without logs and trace points, you cannot reason about correctness. From a systems perspective, the tricky part is coordinating concurrency without introducing races. Even in a single-threaded loop, multiple events can arrive in the same tick, so you need deterministic ordering. This is why many implementations keep a strict sequence: read, update state, compute diff, render. Another subtlety is error handling and recovery. A robust design treats errors as part of the normal control flow: EOF is expected, partial reads are expected, and transient failures must be retried or gracefully handled. The deep dive should also cover how to observe the system, because without logs and trace points, you cannot reason about correctness.
-
How this fit on projects This concept is the backbone of the project because it defines how data and control flow move through the system.
-
Definitions & key terms
- split -> layout operation that divides a rectangle into two regions
- ratio -> fractional size assigned to each split child
- leaf -> a layout tree node that corresponds to a pane
- min size -> minimum width/height constraints for a pane
-
Mental model diagram (ASCII)
[Input] -> [Layout Trees and Resize Propagation] -> [State] -> [Output]
-
How it works (step-by-step, with invariants and failure modes)
- Represent splits as binary nodes
- Compute child rectangles
- Clamp to minimum sizes
- Propagate resize to leaves
-
Minimal concrete example
Vertical split at ratio 0.5 -> left width = floor(W*0.5).
-
Common misconceptions
- “Ratios always preserved” -> min-size clamps can override ratios.
-
Check-your-understanding questions
- How do you store split direction?
- How do you resize without collapsing panes?
-
Check-your-understanding answers
- Use node metadata for split orientation.
- Clamp sizes and adjust sibling.
-
Real-world applications
- tmux layouts
- tiling window managers
-
Where you’ll apply it
- See Section 3.2 Functional Requirements and Section 5.4 Concepts You Must Understand First.
- Also used in: Project 8: Detach/Attach Server Architecture, Project 10: Session and Window Hierarchy.
-
References
- Sedgewick Algorithms - Trees
-
Key insights Layout Trees and Resize Propagation works best when you treat it as a stateful contract with explicit invariants.
-
Summary You now have a concrete mental model for Layout Trees and Resize Propagation and can explain how it affects correctness and usability.
-
Homework/Exercises to practice the concept
- Draw a layout tree for three panes.
-
Solutions to the homework/exercises
- Root split vertical, right child split horizontal.
3. Project Specification
3.1 What You Will Build
A layout engine that represents pane splits as a tree and computes pane rectangles for any terminal size.
3.2 Functional Requirements
- Requirement 1: Support vertical and horizontal splits
- Requirement 2: Maintain minimum pane sizes
- Requirement 3: Recompute layout on resize
- Requirement 4: Persist split ratios
3.3 Non-Functional Requirements
- Performance: Avoid blocking I/O; batch writes when possible.
- Reliability: Handle partial reads/writes and cleanly recover from disconnects.
- Usability: Provide clear CLI errors, deterministic output, and helpful logs.
3.4 Example Usage / Output
$ ./mytmux split -h
[layout] split pane 0 into panes 0 and 1
[exit code: 0]
$ ./mytmux split -h --min-size 5 --cols 8
[error] terminal too small for split
[exit code: 2]
3.5 Data Formats / Schemas / Protocols
Layout tree nodes: split direction + ratio or leaf pane id.
3.6 Edge Cases
- Minimum size violation
- Deeply nested splits
3.7 Real World Outcome
This section defines a deterministic, repeatable outcome. Use fixed inputs and set TZ=UTC where time appears.
3.7.1 How to Run (Copy/Paste)
make
./mytmux split -h
3.7.2 Golden Path Demo (Deterministic)
The “success” demo below is a fixed scenario with a known outcome. It should always match.
3.7.3 If CLI: provide an exact terminal transcript
$ ./mytmux split -h
[layout] split pane 0 into panes 0 and 1
[exit code: 0]
Failure Demo (Deterministic)
$ ./mytmux split -h --min-size 5 --cols 8
[error] terminal too small for split
[exit code: 2]
3.7.8 If TUI
At least one ASCII layout for the UI:
+------------------------------+
| Pane Splitting and Layouts |
| [content area] |
| [status / hints] |
+------------------------------+
4. Solution Architecture
4.1 High-Level Design
+-----------+ +-----------+ +-----------+
| Client | <-> | Server | <-> | PTYs |
+-----------+ +-----------+ +-----------+
4.2 Key Components
| Component | Responsibility | Key Decisions | |-----------|----------------|---------------| | Layout tree | Represents splits and leaves. | Binary tree with split direction. | | Layout solver | Computes rectangles from tree. | Clamp sizes and redistribute slack. | | Serializer | Saves/loads layout. | Use simple pre-order encoding. |
4.4 Data Structures (No Full Code)
struct Node { int split; float ratio; struct Node *a, *b; int pane_id; };
4.4 Algorithm Overview
Key Algorithm: Recursive layout solve
- Start with full rect
- Split by ratio
- Clamp min sizes
- Recurse to leaves
Complexity Analysis:
- O(n) nodes
5. Implementation Guide
5.1 Development Environment Setup
cc --version
make --version
5.2 Project Structure
layout-tree/
|-- src/
| |-- layout.c
| `-- main.c
`-- Makefile
5.3 The Core Question You’re Answering
“How do you partition a rectangle into panes and keep it consistent on resize?”
5.4 Concepts You Must Understand First
- layout trees
- Why it matters and how it impacts correctness.
- split ratios
- Why it matters and how it impacts correctness.
- resize propagation
- Why it matters and how it impacts correctness.
5.5 Questions to Guide Your Design
- How do you choose default ratios?
- How do you enforce min sizes?
5.6 Thinking Exercise
Draw rectangles for a 120x30 terminal with a 2x2 split.
5.7 The Interview Questions They’ll Ask
- How do you represent splits in code?
- How do you resize all panes?
5.8 Hints in Layers
- Use recursion with rectangles.
-
Store min size per pane.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | Trees | Algorithms (Sedgewick) | Trees chapter |
5.10 Implementation Phases
Phase 1: Foundation (2-3 weeks)
Goals:
- Establish the core data structures and loop.
- Prove basic I/O or rendering works.
Tasks:
- Implement the core structs and minimal main loop.
- Add logging for key events and errors.
Checkpoint: You can run the tool and see deterministic output.
Phase 2: Core Functionality (2-3 weeks)
Goals:
- Implement the main requirements and pass basic tests.
- Integrate with OS primitives.
Tasks:
- Implement remaining functional requirements.
- Add error handling and deterministic test fixtures.
Checkpoint: All functional requirements are met for the golden path.
Phase 3: Polish & Edge Cases (2-3 weeks)
Goals:
- Handle edge cases and improve UX.
- Optimize rendering or I/O.
Tasks:
- Add edge-case handling and exit codes.
- Improve logs and documentation.
Checkpoint: Failure demos behave exactly as specified.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| I/O model | blocking vs non-blocking | non-blocking | avoids stalls in multiplexed loops |
| Logging | text vs binary | text for v1 | easier to inspect and debug |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Validate components | parser, buffer, protocol |
| Integration Tests | Validate interactions | end-to-end CLI flow |
| Edge Case Tests | Handle boundary conditions | resize, invalid input |
6.2 Critical Test Cases
- Split + resize results in valid rectangles
- Min size never violated
6.3 Test Data
text
Resize from 120x30 to 80x20 and verify pane sizes.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Pane collapses | No min-size enforcement | Clamp and adjust sibling. |
7.2 Debugging Strategies
- Print layout tree and computed rects.
7.3 Performance Traps
-
Recomputing full layout on every key without caching.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add swap-pane operation.
- Add equalize sizes.
8.2 Intermediate Extensions
- Add zoom pane (temporary fullscreen).
8.3 Advanced Extensions
- Add layout serialization and restore.
9. Real-World Connections
9.1 Industry Applications
- Tilings UIs
- terminal multiplexers
9.2 Related Open Source Projects
- tmux
- i3
9.3 Interview Relevance
- Event loops, terminal I/O, and state machines are common interview topics.
10. Resources
10.1 Essential Reading
- Algorithms by Sedgewick - Trees
10.2 Video Resources
- Layout trees explained (talk).
10.3 Tools & Documentation
- graphviz (visualize trees): graphviz (visualize trees)
10.4 Related Projects in This Series
- Project 8: Detach/Attach Server Architecture - Builds prerequisites
-
Project 10: Session and Window Hierarchy - Extends these ideas
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the core concept without notes
- I can explain how input becomes output in this tool
- I can explain the main failure modes
11.2 Implementation
- All functional requirements are met
- All test cases pass
- Code is clean and well-documented
- Edge cases are handled
11.3 Growth
- I can identify one thing I’d do differently next time
- I’ve documented lessons learned
- I can explain this project in a job interview
12. Submission / Completion Criteria
Minimum Viable Completion:
- Tool runs and passes the golden-path demo
- Deterministic output matches expected snapshot
- Failure demo returns the correct exit code
Full Completion:
- All minimum criteria plus:
- Edge cases handled and tested
- Documentation covers usage and troubleshooting
Excellence (Going Above & Beyond):
- Add at least one advanced extension
- Provide a performance profile and improvement notes