Project 12: TUI IDE with LSP

A minimal terminal IDE with file explorer, editor pane, and diagnostics panel using LSP.

Quick Reference

Attribute Value
Difficulty Level 5: Master (REFERENCE.md)
Time Estimate 4-6 weeks
Main Programming Language Rust or Python
Alternative Programming Languages Go, C++
Coolness Level Level 5: Pure Magic (REFERENCE.md)
Business Potential Level 3: Service & Support (REFERENCE.md)
Prerequisites Concurrency and External Integration, Input Modes and Key Decoding, Screen Rendering, Buffering, and Diffing
Key Topics Protocol Integration

1. Learning Objectives

By completing this project, you will:

  1. Build and validate the core behavior described in the real-world outcome.
  2. Apply Concurrency and External Integration, Input Modes and Key Decoding, Screen Rendering, Buffering, and Diffing to a working TUI.
  3. Design a predictable input-to-rendering pipeline with explicit state changes.
  4. Produce a tool that behaves consistently across terminals and restores state on exit.

2. All Theory Needed (Per-Concept Breakdown)

Concurrency and External Integration (Async I/O and LSP)

Fundamentals Real TUI tools rarely operate in isolation: they run subprocesses, stream logs, and integrate with external protocols. You need asynchronous I/O so your UI stays responsive while waiting on disk or network. The Language Server Protocol (LSP) is a key example of external integration: it defines a JSON-RPC based protocol between an editor and a language server, with messages framed by Content-Length headers. citeturn8view0turn6search7 Understanding this framing and the request/response/notification model is critical for building a terminal IDE. Concurrency is not just a performance feature here; it is what keeps input and rendering from freezing while external work is happening.

Deep Dive into the concept A responsive TUI is an event-driven system: it must handle user input, background tasks, and rendering without blocking. This often requires non-blocking I/O or separate worker threads that communicate with the main loop via message queues. The architecture typically resembles a reactor: events enter a queue, state updates occur, and rendering happens on a fixed cadence or after each update. The challenge is coordinating asynchronous tasks without race conditions.

External integration often uses structured protocols. LSP is a canonical example for TUI IDEs. LSP defines a JSON-RPC based message format, and frames messages with a header that includes Content-Length (required) and Content-Type (optional). The header is separated from the JSON content by a blank line (double CRLF). citeturn8view0 The official LSP overview explains that the protocol standardizes communication between development tools and language servers. citeturn6search7 This means your TUI must implement a message framing layer: read bytes from the server, parse headers, then parse JSON payloads, then dispatch responses.

Another common integration pattern is log streaming. For example, a TUI log viewer needs to tail a file or subscribe to a stream without blocking input handling. This suggests a concurrency model where background workers push events (new lines) into the UI thread. You must also handle backpressure: if logs arrive faster than you can render, you need buffering and drop strategies.

Finally, clean shutdown matters. When you spawn subprocesses (Git, LSP servers), you must terminate them on exit and restore terminal state. A robust TUI uses a dedicated lifecycle: start external processes, manage their I/O, and ensure cleanup paths on error or signals.

Protocol integration also introduces ordering and correlation problems. JSON-RPC supports requests, responses, and notifications; requests carry an ID so the client can match responses. Your TUI must maintain a table of in-flight requests and timeouts so it can recover if a server is slow or unresponsive. This is not just for correctness; it also affects UI behavior. If you wait indefinitely for a response, you can block features like diagnostics or go-to-definition.

Cancellation is another practical concern. If the user types rapidly or navigates to a different file, outstanding requests may become irrelevant. A well-designed system can cancel or ignore outdated responses to avoid flicker or confusing UI updates. This requires you to tag requests with the state they were generated from and discard responses that no longer apply.

Error handling should be explicit. External processes can crash, JSON can be malformed, and connections can drop. Your event loop must treat these as normal events and surface them to the user in a non-blocking way. For example, a TUI IDE might show a diagnostics banner when the LSP server disconnects, while continuing to allow local editing.

You should also design for flow control. If your UI can only render 30 frames per second but your input stream can produce 1,000 messages per second, you need a strategy to coalesce or drop events. This is especially important for log viewers and diagnostics streams. A small, well-defined queue with clear drop rules keeps the UI stable under load.

How this fits on projects

  • Project 11 (Async Log Monitor), Project 12 (TUI IDE with LSP)

Definitions & key terms

  • JSON-RPC: Remote procedure call protocol encoded in JSON. citeturn8view0
  • LSP: Protocol between editors and language servers using JSON-RPC. citeturn6search7
  • Content-Length framing: LSP messages are preceded by a header specifying byte length. citeturn8view0

Mental model diagram

User Events -> UI Loop -> State -> Render
                 ^
                 |
        Background Tasks (I/O, subprocess, LSP)
                 |
              Messages

How it works

  1. Start background workers or subprocesses.
  2. Read and parse their output into structured events.
  3. Send events into the main loop.
  4. Update state and render without blocking input.

Minimal concrete example

PROTOCOL TRANSCRIPT (LSP framing):
Content-Length: 85\r\n
\r\n
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { ... } }

Common misconceptions

  • “Async means multi-threaded” -> It can be single-threaded with non-blocking I/O.
  • “Parsing JSON-RPC is trivial” -> You must handle framing and partial reads.

Check-your-understanding questions

  1. Why is Content-Length framing required in LSP?
  2. What happens if a background task blocks the main loop?
  3. How would you handle a burst of log lines?

Check-your-understanding answers

  1. To delimit messages in a continuous byte stream.
  2. The UI freezes and input becomes unresponsive.
  3. Buffer lines and apply backpressure or drop strategy.

Real-world applications

  • Terminal IDEs, log monitors, deployment dashboards

Where you’ll apply it

  • Project 11, Project 12

References

  • LSP specification base protocol (header + JSON-RPC content) citeturn8view0
  • LSP official overview (protocol purpose) citeturn6search7
  • “Operating Systems: Three Easy Pieces” - Ch. on concurrency

Key insights Responsive TUIs are event-driven systems that must parse and integrate external streams safely.

Summary Concurrency and protocol integration are essential for real tools; they require framing, parsing, and careful event-loop design.

Homework/Exercises to practice the concept

  1. Design an event queue schema that can carry input, timer ticks, and LSP messages.
  2. Describe a graceful shutdown sequence for a TUI that spawns subprocesses.

Solutions to the homework/exercises

  1. Use a tagged union: InputEvent, TimerEvent, LspEvent, LogEvent.
  2. Stop input loop -> terminate subprocess -> drain output -> restore terminal state.

Input Modes and Key Decoding (termios Line Discipline)

Fundamentals Terminal input is not delivered to your program exactly as the user types it. The terminal driver applies a line discipline that can buffer input, handle editing keys, and interpret control characters. POSIX termios defines canonical (line-based) and non-canonical (raw-ish) input processing. In canonical mode, input is delivered line-by-line and read() waits until newline or EOF; editing keys like erase and kill are handled by the driver. In non-canonical mode, input is delivered immediately and the driver does not perform line editing. This distinction is essential for interactive TUIs because you need keypress-level input, not line-buffered input. citeturn1search3

Deep Dive into the concept The termios interface defines how a terminal device processes input and output. Canonical mode (often called “cooked”) is designed for typical shell usage: the driver buffers input until it sees a line delimiter, then passes it to the program. This means your program cannot react to individual keypresses in real time, and special characters like Backspace are processed before the program ever sees them. In contrast, non-canonical mode disables line buffering and line editing, and uses the MIN and TIME settings to control when read() returns. This lets a TUI receive bytes immediately and implement its own key handling.

Raw mode in modern libraries is usually a bundle of termios changes: disable canonical input, disable echo, and disable signal generation for control characters. This is crucial for handling keys like Ctrl+C yourself. Libraries like crossterm summarize the effects: input is no longer line buffered, special keys are not processed by the terminal driver, and input is delivered byte-by-byte. citeturn3search2

Once in non-canonical/raw mode, you must interpret input bytes. Many keys are sent as multi-byte escape sequences (for example, arrow keys often begin with ESC). These sequences are not standardized across all terminals, which is why terminfo also contains key capabilities. Your input decoder must handle partial sequences, timeouts, and ambiguous prefixes (ESC alone vs ESC as the start of a sequence). A robust approach is to implement a small state machine: when you see ESC, start collecting bytes; if a complete known sequence is matched, emit a high-level key event; if timeout occurs, treat ESC as a standalone key.

Another key aspect is signals and terminal restoration. If your program is interrupted (SIGINT, SIGTERM) while in raw mode, the terminal may remain in a broken state. A reliable TUI sets up cleanup handlers to restore termios state on exit, and uses an alternate screen buffer to keep the user’s shell clean.

Finally, input in TUIs is not just keys. Resize events are delivered via signals (like SIGWINCH) or via platform-specific APIs, and mouse input can be enabled in some terminals. In your architecture, all of these should be normalized into a single event stream to keep update logic deterministic.

Non-canonical mode uses two parameters, often called VMIN and VTIME, to control when reads return. This lets you trade off latency and CPU usage. A common approach is to request at least one byte and use a short timeout, which yields responsive input without a busy loop. For portability, you should treat these parameters as a contract: they define the boundary between polling and blocking behavior in your main loop.

The line discipline can also transform bytes (for example, carriage return to newline) and handle special control characters. When you disable canonical processing, these transformations often stop, which means you must decide how to handle them yourself. This is why a raw-mode TUI frequently treats Enter, Backspace, and Tab as high-level events that it interprets explicitly.

Key decoding is best done in layers: a byte reader, a sequence parser, and an event normalizer. The parser recognizes known escape sequences and converts them into symbolic events, while the normalizer maps them into consistent key names across platforms. This layered design lets you test the parser independently from the rest of the UI, and it makes it easier to support both simple keys (single byte) and complex keys (multi-byte sequences).

How this fits on projects

  • Project 2 (Raw Key Decoder), Project 5 (ncurses Dashboard), Project 8 (Bubble Tea Git Client)

Definitions & key terms

  • Canonical mode: Line-buffered input processing; read() returns after newline/EOF. citeturn1search3
  • Non-canonical mode: Byte-level input without line editing. citeturn1search3
  • Raw mode: A common configuration that disables echo, line buffering, and special-key processing. citeturn3search2
  • Escape sequence: Multi-byte sequences starting with ESC representing special keys.

Mental model diagram

Keyboard -> Driver Buffer -> termios line discipline -> read() -> Key Decoder -> Events
            (canonical or raw)                                (state machine)

How it works

  1. Configure termios to canonical or non-canonical mode.
  2. Read bytes from stdin as they arrive.
  3. Feed bytes into a decoder that recognizes escape sequences.
  4. Emit high-level events (Up, Down, Ctrl+C, etc.).
  5. Feed events into your app’s update loop.

Minimal concrete example

PSEUDOCODE:
SET_INPUT_MODE(raw)
WHILE running:
  bytes = READ_NONBLOCKING()
  FOR b IN bytes:
    decoder.feed(b)
    IF decoder.has_event():
      event = decoder.pop_event()
      dispatch(event)

Common misconceptions

  • “Raw mode means no rules” -> It still follows MIN/TIME semantics.
  • “Arrow keys are single bytes” -> They are usually escape sequences.
  • “SIGINT always means exit” -> You can capture Ctrl+C in raw mode.

Check-your-understanding questions

  1. Why does canonical mode prevent real-time key handling?
  2. What is the role of MIN and TIME in non-canonical mode?
  3. Why must you restore terminal state on exit?

Check-your-understanding answers

  1. Input is buffered until newline/EOF, so keypresses are not delivered immediately.
  2. They control when read() returns in byte-oriented input.
  3. Otherwise the user’s terminal can remain in raw/no-echo mode.

Real-world applications

  • TUIs, terminal games, interactive debuggers, SSH-based tools

Where you’ll apply it

  • Project 2, Project 5, Project 8, Project 12

References

  • POSIX termios input processing (canonical vs non-canonical) citeturn1search3
  • crossterm raw mode behavior (summary of effects) citeturn3search2
  • “Advanced Programming in the UNIX Environment” - Ch. 18

Key insights Raw input is a controlled state machine, not a free-for-all stream.

Summary Input handling is a layered pipeline; understanding termios is required to build reliable keyboard-driven interfaces.

Homework/Exercises to practice the concept

  1. Draw a state machine for decoding ESC-based arrow keys.
  2. Describe how you would detect a resize event in your app.

Solutions to the homework/exercises

  1. Start in NORMAL; on ESC move to ESC_SEEN; accept ‘[’ then digits; map known final byte to key.
  2. Use a signal handler for window resize and push a Resize event into your loop.

Screen Rendering, Buffering, and Diffing

Fundamentals Rendering in TUIs is about mapping a logical state to a 2D grid of cells and emitting the minimal set of updates. Unlike GUIs, you do not have a compositor; you are the compositor. The screen is a matrix of characters with attributes (color, bold, underline). To avoid flicker and wasted output, you typically keep a back buffer (last frame) and compute a diff to the new frame. The diff tells you which cells changed and where to move the cursor. Many libraries implement this for you, but you need to understand it to debug performance or visual glitches. A frame is a full snapshot of the terminal grid, and your renderer is responsible for ensuring that each frame is consistent and complete.

Deep Dive into the concept A screen buffer is a 2D array of cells, where each cell has a glyph and style attributes. A frame is a complete snapshot of this buffer. When your app state changes, you generate a new frame. If you were to naively clear the screen and print the entire frame every time, performance would degrade on slow terminals and over SSH. This is why most TUI systems use double buffering and diffing.

Diffing can be done at multiple granularities. The simplest method is to compare cell-by-cell and emit updates for every change, moving the cursor as needed. A more efficient method groups contiguous runs of cells in the same row to reduce cursor movements. Another technique is damage tracking: instead of comparing full frames, you track which regions changed as a result of state updates. The tradeoff is complexity vs performance.

A robust renderer also handles terminal-specific features like alternate screen buffers. Alternate screen buffer usage prevents your UI from polluting the shell scrollback; when you exit, the original screen is restored. Libraries like crossterm expose entry and exit commands for alternate screens. citeturn2search2

The critical invariants are: (1) you must know where the cursor is, (2) you must know the current style attributes, and (3) you must restore these on exit. A diffing renderer is essentially a small compiler that transforms an intended frame into a minimal control-sequence program. This is the core of performance optimization in TUIs.

In projects, you will build a simplified diff engine: keep a 2D array of cells for the previous frame, compute a new frame each tick, and emit only the changed cells. You will learn to coalesce updates, manage cursor moves, and reduce redundant style changes.

There are also subtle correctness issues. For example, if you update a cell with a different style, you must ensure the style is set before printing the character, and you must avoid leaking that style into subsequent cells. This means your renderer needs a model of the current style state in the terminal and must emit style reset sequences at the right time. Similarly, when rendering wide characters or combining characters, you must consider how many columns a glyph occupies; otherwise your cursor calculations will drift. Even if you avoid complex Unicode in your own output, the terminal width can vary by locale, so your renderer should treat width as a property of a glyph rather than as a constant.

Resizing adds another layer. When the terminal size changes, your frame dimensions change, and a previously valid cursor position may be out of bounds. A robust renderer clamps or reflows content, re-creates buffers to the new size, and forces a full redraw to prevent artifacts from stale rows. For dashboards, you often choose a strategy: either truncate content to fit the new size or change layout to a stacked mode.

Finally, you must consider how frequently to render. Some TUIs render only on state changes, others render on a fixed tick. Rendering on every tick simplifies animations but can waste bandwidth. Rendering on state change reduces output but requires careful invalidation logic. The projects will give you a chance to compare both approaches and measure their impact.

How this fits on projects

  • Project 4 (Screen Diff Renderer), Project 5 (ncurses Dashboard), Project 9 (Ratatui Dashboard)

Definitions & key terms

  • Back buffer: The previous frame stored for diffing.
  • Damage tracking: Tracking which regions changed to reduce diff work.
  • Alternate screen: A separate buffer that restores the original screen on exit. citeturn2search2

Mental model diagram

State -> Frame A (buffer) -> diff(Frame A, Frame B) -> Emit sequences -> Screen
               ^                                   |
               |                                   v
            Frame B <--------------------------- Next tick

How it works

  1. Generate a full frame from app state.
  2. Compare with the previous frame.
  3. For each changed region, emit cursor moves and text.
  4. Update the stored previous frame.

Minimal concrete example

PSEUDOCODE:
new_frame = render(state)
for each cell in grid:
  if new_frame[cell] != old_frame[cell]:
    move_cursor(cell.x, cell.y)
    set_style(cell.style)
    write(cell.glyph)
old_frame = new_frame

Common misconceptions

  • “Diffing is only for speed” -> It also prevents flicker and keeps cursor stable.
  • “You can ignore cursor position” -> Incorrect; cursor drift causes corrupted layouts.

Check-your-understanding questions

  1. Why is a diff-based renderer faster over SSH?
  2. What happens if you fail to restore the cursor or styles on exit?
  3. Why might you prefer damage tracking to full diffing?

Check-your-understanding answers

  1. It emits fewer bytes and avoids full-screen clears.
  2. The user’s terminal remains in a modified state.
  3. It reduces diff cost by focusing on known dirty regions.

Real-world applications

  • Terminal dashboards, log viewers, system monitors

Where you’ll apply it

  • Project 4, Project 5, Project 9, Project 11

References

  • crossterm terminal features (alternate screen) citeturn2search2
  • “The Pragmatic Programmer” - Ch. “Orthogonality” (designing clear rendering stages)

Key insights Rendering is a compiler: state is compiled into the smallest correct terminal program.

Summary Efficient TUIs rely on diff-based rendering and strict state management to avoid flicker and maintain control over the screen.

Homework/Exercises to practice the concept

  1. Design a diff strategy that minimizes cursor moves.
  2. Create a list of rendering invariants you will enforce in every frame.

Solutions to the homework/exercises

  1. Group contiguous changes by row and emit a single cursor move per group.
  2. Always reset styles, hide/show cursor explicitly, and restore terminal state on exit.

3. Project Specification

3.1 What You Will Build

A minimal terminal IDE with file explorer, editor pane, and diagnostics panel using LSP.

Included:

  • The core UI flow described in the Real World Outcome
  • Deterministic input handling and rendering
  • Clean exit and terminal state restoration

Excluded:

  • GUI features, mouse-first workflows, or non-terminal frontends
  • Networked collaboration or cloud sync

3.2 Functional Requirements

  1. Core Interaction: Implements the main interaction loop and updates the screen correctly.
  2. Input Handling: Handles required keys without blocking and supports quit/exit.
  3. Rendering: Updates only what changes to avoid flicker.
  4. Resize Handling: Adapts to terminal resize or shows a clear warning state.
  5. Errors: Handles invalid input or missing data gracefully.

3.3 Non-Functional Requirements

  • Performance: Stable refresh without visible flicker under normal usage.
  • Reliability: Terminal state is restored on exit or error.
  • Usability: Keyboard-first navigation with clear status/help hints.

3.4 Example Usage / Output

$ ./tui-ide

$ ./tui-ide project/ [Explorer] [Editor] [Diagnostics] main.py def foo(): 1:1 unused import …

Keys: Ctrl+S save, F12 go-to-def, q quit


ASCII layout:

[Left: file tree] [Center: editor] [Right: diagnostics] [Footer: status]

3.5 Data Formats / Schemas / Protocols

  • Screen Model: 2D grid of cells with glyph + style
  • Input Events: Normalized key events (Up, Down, Enter, Esc, Ctrl)
  • State Snapshot: Immutable model used for rendering each frame

3.6 Edge Cases

  • Terminal resized to smaller than minimum layout
  • Rapid key repeat and partial escape sequences
  • Missing or invalid input file (if applicable)
  • Unexpected termination (SIGINT)

3.7 Real World Outcome

3.7.1 How to Run (Copy/Paste)

$ ./tui-ide

3.7.2 Golden Path Demo (Deterministic)

  • Launch the tool
  • Perform the primary action once
  • Observe the expected screen update

3.7.3 If CLI: provide an exact terminal transcript

$ ./tui-ide

$ ./tui-ide project/ [Explorer] [Editor] [Diagnostics] main.py def foo(): 1:1 unused import …

Keys: Ctrl+S save, F12 go-to-def, q quit


ASCII layout:

[Left: file tree] [Center: editor] [Right: diagnostics] [Footer: status]

3.7.4 Failure Demo (Deterministic)

$ ./tui-ide --bad-flag
ERROR: unknown option: --bad-flag
exit code: 2

4. Solution Architecture

4.1 High-Level Design

Input -> Event Queue -> State Update -> Render -> Terminal

4.2 Key Components

Component Responsibility Key Decisions
Input Decoder Normalize raw input into events Handle partial sequences safely
State Model Hold UI state and selections Keep state immutable per frame
Renderer Draw from state to terminal Diff-based updates
Controller Orchestrate loop and timers Non-blocking IO

4.3 Data Structures (No Full Code)

DATA STRUCTURE: Cell
- glyph
- fg_color
- bg_color
- attrs

DATA STRUCTURE: Frame
- width
- height
- cells[width][height]

4.4 Algorithm Overview

Key Algorithm: Render Diff

  1. Build new frame from current state
  2. Compare with old frame
  3. Emit minimal updates for changed cells

Complexity Analysis:

  • Time: O(width * height)
  • Space: O(width * height)

5. Implementation Guide

5.1 Development Environment Setup

# Build and run with your toolchain

5.2 Project Structure

project-root/
|-- src/
|-- tests/
|-- assets/
`-- README.md

5.3 The Core Question You’re Answering

“How do I integrate a real-world protocol into a responsive TUI?”

5.4 Concepts You Must Understand First

  1. LSP framing
    • What is Content-Length and why is it required?
    • Book Reference: “Operating Systems: Three Easy Pieces” - Concurrency
  2. Text buffer design
    • How do you model cursor and selection?
    • Book Reference: “Algorithms, Fourth Edition” - Ch. 1

5.5 Questions to Guide Your Design

  1. Protocol integration
    • How will you parse and dispatch LSP messages?
    • How will you queue outgoing requests?
  2. Editor model
    • How will you support insert/delete efficiently?
    • How will you keep diagnostics in sync?

5.6 Thinking Exercise

Design the LSP Message Pipeline

Sketch how bytes become JSON-RPC messages.

Questions to answer:

  • How do you handle partial reads?
  • How do you match responses to requests?

5.7 The Interview Questions They’ll Ask

  1. “How does LSP framing work?”
  2. “What is JSON-RPC used for?”
  3. “How do you keep an editor responsive while waiting for LSP?”
  4. “How do you model cursor and selection?”
  5. “What happens when the server crashes?”

5.8 Hints in Layers

Hint 1: Start with a static editor Render a single file without LSP.

Hint 2: Add LSP handshake Implement initialize and shutdown messages.

Hint 3: Pseudocode

read_stream -> parse_headers -> parse_json -> dispatch

Hint 4: Debugging Log raw LSP messages to a file for inspection.


5.9 Books That Will Help

Topic Book Chapter
Concurrency “Operating Systems: Three Easy Pieces” Concurrency
Text processing “Algorithms, Fourth Edition” Ch. 1

5.10 Implementation Phases

Phase 1: Foundation

Goals:

  • Initialize the terminal and input handling
  • Render the first static screen

Tasks:

  1. Implement setup and teardown
  2. Draw a static layout that matches the Real World Outcome

Checkpoint: The UI renders and exits cleanly.

Phase 2: Core Functionality

Goals:

  • Implement the main interaction loop
  • Update state based on input

Tasks:

  1. Add event processing
  2. Implement the main feature (draw, navigate, filter)

Checkpoint: The primary interaction works end-to-end.

Phase 3: Polish & Edge Cases

Goals:

  • Handle resizing and invalid input
  • Improve performance and usability

Tasks:

  1. Add resize handling
  2. Add error states and help hints

Checkpoint: No flicker and clean recovery from edge cases.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Input model raw vs canonical raw Required for key-level input
Render strategy full redraw vs diff diff Avoid flicker and reduce output
State model mutable vs immutable immutable Predictable updates and testing

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Validate parsing and state transitions Key decoder tests
Integration Tests Verify rendering pipeline Frame diff vs expected
Edge Case Tests Terminal resize and invalid input Small terminal size

6.2 Critical Test Cases

  1. Resize: Shrink terminal below minimum and verify warning.
  2. Rapid Input: Hold down keys and ensure no crash.
  3. Exit: Force quit and verify terminal restoration.

6.3 Test Data

Input sequence: Up, Up, Down, Enter
Expected: selection moves and activates without crash

7. Common Pitfalls & Debugging

Problem 1: “LSP messages corrupted”

  • Why: Content-Length parsing wrong.
  • Fix: Buffer until full payload length is available.
  • Quick test: Log header and payload sizes.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a help overlay with keybindings
  • Add a status bar with timestamps

8.2 Intermediate Extensions

  • Add configurable themes
  • Add persistent settings file

8.3 Advanced Extensions

  • Add plugin hooks for new views
  • Add performance tracing for render time

9. Real-World Connections

9.1 Industry Applications

  • Terminal dashboards for infrastructure monitoring
  • Developer tools used over SSH and in containers
  • htop, ranger, lazygit, nmtui (for UI design reference)

9.3 Interview Relevance

  • Input handling, event loops, and state modeling questions

10. Resources

10.1 Essential Reading

  • “Operating Systems: Three Easy Pieces”

10.2 Video Resources

  • Conference talks on terminal UI architecture (choose one and take notes)

10.3 Tools & Documentation

  • terminfo, curses, or framework docs used in this project
  • See other projects in this folder for follow-on ideas

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain the rendering pipeline for this project
  • I can explain how input is decoded and normalized
  • I can explain how my UI state updates per event

11.2 Implementation

  • All functional requirements are met
  • All critical test cases pass
  • Edge cases are handled and documented

11.3 Growth

  • I documented lessons learned
  • I can explain this project in a job interview

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Program runs and matches Real World Outcome
  • Terminal state restored on exit
  • Main interaction works

Full Completion:

  • All minimum criteria plus:
  • Resize handling and error states
  • Tests for core parsing and rendering

Excellence (Going Above & Beyond):

  • Performance profiling results included
  • Additional features from Extensions completed