▓▓▒▒ FLOAT.BBS ▒▒▓▓
Document: imprints/sysops-daydream/2025-10-26-tui-foundations-working/2025-10-17-rust-tui-architecture-guide
      
← Back to SYSOPS.DAYDREAM

Rust TUI Architecture Guide

From Layout Patterns to Production Implementation

Last Updated: 2025-10-17 Context: Synthesized from terminal UI explorations, Angular ui-router patterns, Vue component systems, and ratatui experiments


Overview: Why TUI Layout Architecture Matters

Terminal UIs aren’t just “simple GUIs” - they operate under different constraints and affordances:

The architecture you choose determines:


Foundational Concepts

Terminal Coordinate Systems

Terminals are fundamentally 2D character grids:

// Terminal grid: (0,0) at top-left
// Each cell holds one character + style attributes
struct Cell {
    character: char,
    fg_color: Color,
    bg_color: Color,
    modifiers: Modifiers, // bold, italic, underline, etc.
}

type TerminalGrid = Vec<Vec<Cell>>;

Coordinate mapping:

// Metal NDC (-1 to 1) requires conversion for low-level rendering
fn terminal_to_ndc(row: u16, col: u16, grid_size: (u16, u16)) -> (f32, f32) {
    let x = (col as f32 / grid_size.1 as f32) * 2.0 - 1.0;
    let y = 1.0 - (row as f32 / grid_size.0 as f32) * 2.0; // Y-flip for top-left origin
    (x, y)
}

Layout Constraints vs. Absolute Positioning

Absolute positioning:

// Direct control, breaks on resize
render_widget(widget, Rect::new(10, 5, 50, 20));

Constraint-based:

// Declares intent, adapts to available space
let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([
        Constraint::Length(3),      // Fixed: header
        Constraint::Min(10),        // Flexible: content
        Constraint::Percentage(20), // Proportional: sidebar
    ])
    .split(area);

Trade-off: Absolute = precise control, constraint = responsive adaptation.

State Management Patterns

Immediate mode (no persistent state):

fn render(terminal: &mut Terminal) {
    terminal.draw(|f| {
        // Rebuild UI from scratch every frame
        if button(f, "Click") { /* action */ }
    })?;
}

Retained mode (persistent widget tree):

struct App {
    root: Container,
    state: AppState,
}

impl App {
    fn update(&mut self, event: Event) {
        self.root.handle_event(event, &mut self.state);
    }
    
    fn render(&self, terminal: &mut Terminal) {
        self.root.render(terminal);
    }
}

Layout Paradigm Comparison

PatternMental ModelWhen to UseTrade-offs
Constraint-BasedCSS Flexbox/GridResponsive layouts, terminal resize mattersLess precise control, learning curve
Immediate ModeGame engine UI (ImGui)Animations, simple state, rapid prototypingFull redraw cost, no widget persistence
Component TreeReact/Vue SPAComplex state, functional compositionDiffing overhead, memory for virtual tree
Event-Driven WidgetsGTK/QtTraditional GUI patterns, long-lived widgetsBoilerplate, callback spaghetti risk
Multiplexertmux/screen panesIndependent views, process isolationIPC complexity, coarse-grained layout
Grid/TableSpreadsheet cellsPrecise control, terminal-nativeManual resize handling, no abstractions
Scene GraphGame engine nodesSpatial transforms, hierarchical renderingOverkill for most TUIs
State Machine RouterAngular ui-routerModal workflows, wizard-like flowsState explosion if overused

Deep Dive: Constraint-Based (ratatui style)

Example: Master-detail layout

use ratatui::{
    layout::{Constraint, Direction, Layout},
    widgets::{Block, Borders, List, Paragraph},
};

fn render_master_detail(f: &mut Frame, state: &AppState) {
    let chunks = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage(30), // List
            Constraint::Percentage(70), // Detail
        ])
        .split(f.size());
    
    // Render list in left pane
    let items: Vec<ListItem> = state.items.iter()
        .map(|item| ListItem::new(item.title.clone()))
        .collect();
    let list = List::new(items)
        .block(Block::default().borders(Borders::ALL).title("Items"));
    f.render_widget(list, chunks[0]);
    
    // Render detail in right pane
    if let Some(selected) = &state.selected_item {
        let detail = Paragraph::new(selected.content.clone())
            .block(Block::default().borders(Borders::ALL).title("Detail"));
        f.render_widget(detail, chunks[1]);
    }
}

Strengths:

Weaknesses:

Deep Dive: Immediate Mode

Example: Simple button widget

fn button(f: &mut Frame, area: Rect, label: &str, focused: bool) -> bool {
    let style = if focused {
        Style::default().bg(Color::Blue).fg(Color::White)
    } else {
        Style::default()
    };
    
    let button = Paragraph::new(label)
        .style(style)
        .block(Block::default().borders(Borders::ALL));
    f.render_widget(button, area);
    
    // Return true if button was "clicked" (Enter pressed while focused)
    focused && matches!(last_event(), Event::Key(KeyCode::Enter))
}

// Usage in main loop:
loop {
    terminal.draw(|f| {
        let area = f.size();
        if button(f, area, "Save", app.focused_on_save) {
            app.save();
        }
    })?;
}

Strengths:

Weaknesses:


JSON-Driven Dynamic Components

Inspired by Vue’s :is component pattern, enable runtime UI modification without recompiling.

Enum Dispatch Pattern (Type-Safe)

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", content = "props")]
enum Component {
    VStack { 
        spacing: u16,
        children: Vec<Component> 
    },
    HStack { 
        spacing: u16,
        children: Vec<Component> 
    },
    Text { 
        content: String,
        style: TextStyle 
    },
    Button { 
        label: String,
        action: String 
    },
    Input { 
        placeholder: String,
        value: String 
    },
    Panel { 
        title: String,
        child: Box<Component> 
    },
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct TextStyle {
    bold: bool,
    italic: bool,
    color: String,
}

impl Component {
    fn render(&self, f: &mut Frame, area: Rect, state: &mut AppState) {
        match self {
            Component::VStack { spacing, children } => {
                let child_height = (area.height - spacing * (children.len() as u16 - 1)) 
                    / children.len() as u16;
                let mut y = area.y;
                for child in children {
                    let child_area = Rect::new(area.x, y, area.width, child_height);
                    child.render(f, child_area, state);
                    y += child_height + spacing;
                }
            }
            Component::Text { content, style } => {
                let mut text_style = Style::default();
                if style.bold { text_style = text_style.add_modifier(Modifier::BOLD); }
                if style.italic { text_style = text_style.add_modifier(Modifier::ITALIC); }
                let widget = Paragraph::new(content.as_str()).style(text_style);
                f.render_widget(widget, area);
            }
            Component::Button { label, action } => {
                let widget = Paragraph::new(label.as_str())
                    .block(Block::default().borders(Borders::ALL));
                f.render_widget(widget, area);
                // Handle button action via state.pending_action = Some(action.clone())
            }
            // ... other component types
            _ => {}
        }
    }
}

JSON Configuration Example

{
  "type": "VStack",
  "props": {
    "spacing": 1,
    "children": [
      {
        "type": "Panel",
        "props": {
          "title": "Terminal Grid Demo",
          "child": {
            "type": "Text",
            "props": {
              "content": "Hello from JSON-driven UI!",
              "style": {
                "bold": true,
                "italic": false,
                "color": "cyan"
              }
            }
          }
        }
      },
      {
        "type": "HStack",
        "props": {
          "spacing": 2,
          "children": [
            {
              "type": "Button",
              "props": {
                "label": "Save",
                "action": "save_config"
              }
            },
            {
              "type": "Button",
              "props": {
                "label": "Cancel",
                "action": "cancel"
              }
            }
          ]
        }
      }
    ]
  }
}

Hot Reload Implementation

use notify::{Watcher, RecursiveMode, watcher};
use std::sync::mpsc::channel;
use std::time::Duration;

struct HotReloadApp {
    config_path: PathBuf,
    root_component: Component,
    watcher: notify::RecommendedWatcher,
}

impl HotReloadApp {
    fn new(config_path: PathBuf) -> Result<Self> {
        let root_component = Self::load_config(&config_path)?;
        
        let (tx, rx) = channel();
        let mut watcher = watcher(tx, Duration::from_secs(1))?;
        watcher.watch(&config_path, RecursiveMode::NonRecursive)?;
        
        Ok(Self {
            config_path,
            root_component,
            watcher,
        })
    }
    
    fn load_config(path: &Path) -> Result<Component> {
        let json = std::fs::read_to_string(path)?;
        let component: Component = serde_json::from_str(&json)?;
        Ok(component)
    }
    
    fn check_reload(&mut self) -> Result<bool> {
        // Non-blocking check for file changes
        if let Ok(_) = self.watcher.try_recv() {
            self.root_component = Self::load_config(&self.config_path)?;
            return Ok(true);
        }
        Ok(false)
    }
}

Benefits:


State Machine Routing (ui-router Pattern)

Translate Angular ui-router’s state-based routing to Rust for modal workflows.

#[derive(Debug, Clone, PartialEq)]
enum AppState {
    Dashboard {
        selected_item: Option<usize>,
    },
    Settings {
        active_tab: SettingsTab,
    },
    Editor {
        file_path: PathBuf,
        dirty: bool,
    },
}

#[derive(Debug, Clone)]
struct Transition {
    from: AppState,
    to: AppState,
    on_exit: Vec<Box<dyn Fn(&mut AppContext)>>,
    on_enter: Vec<Box<dyn Fn(&mut AppContext)>>,
}

struct StateMachine {
    current: AppState,
    transitions: Vec<Transition>,
}

impl StateMachine {
    fn transition_to(&mut self, next: AppState, ctx: &mut AppContext) {
        // Find matching transition
        if let Some(transition) = self.transitions.iter()
            .find(|t| t.from == self.current && t.to == next) 
        {
            // Execute exit hooks
            for hook in &transition.on_exit {
                hook(ctx);
            }
            
            // Update state
            self.current = next.clone();
            
            // Execute enter hooks
            for hook in &transition.on_enter {
                hook(ctx);
            }
        }
    }
}

Named regions (like ui-router’s named views):

struct LayoutRegions {
    header: Option<Region>,
    sidebar: Option<Region>,
    main: Region,
    footer: Option<Region>,
}

// Render component into named region
fn render_in_region(component: &Component, target: &str, regions: &LayoutRegions) {
    match target {
        "header" => regions.header.as_ref().map(|r| component.render(r.area)),
        "sidebar" => regions.sidebar.as_ref().map(|r| component.render(r.area)),
        "main" => component.render(regions.main.area),
        "footer" => regions.footer.as_ref().map(|r| component.render(r.area)),
        _ => None,
    };
}

Connection to Previous Work

October 2025: Terminal Grid Renderer

Current Status: Building Metal-based terminal renderer with:

Integration opportunity: JSON-driven component system can target this grid renderer:

// Component renders to abstract grid
component.render_to_grid(&mut grid_state);

// Grid state translates to Metal scene updates
let updates = grid_state.calculate_updates();
metal_renderer.apply_updates(updates);

March 2025: Multi-Platform Conversation Explorer

Learned patterns:

Applicable here:

October 2025: Rust TUI Explosion (Basalt/ratatui)

Key insight: “This is not metaphor. This is code.”

Consciousness technology stack:

FLOAT.dispatch (cultural layer)

Conversation AST (interface semantics)

Basalt TUI (Obsidian terminal WYSIWYG)

ratatui (widget composition)

Terminal cells (character grid)

Reusable patterns:

September 2025: Egui Exploration

Context shift: Moved from terminal (ratatui) to desktop GUI (egui)

Key difference:

Lesson: Immediate mode works for both TUI and GUI - state management simplicity transfers across rendering backends.


Implementation Roadmap

Phase 1: Minimal Viable Implementation (1 week)

Goal: Render JSON-defined layout with basic components.

Tasks:

  1. Define core Component enum (VStack, HStack, Text, Button)
  2. Implement Component::render() using ratatui primitives
  3. Load JSON config from file
  4. Create simple event loop (redraw on input, no hot reload yet)
  5. Test with 2-3 example layouts

Dependencies:

[dependencies]
ratatui = "0.25"
crossterm = "0.27"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Deliverable: Binary that loads layout.json and renders it.

Phase 2: Core Features (2 weeks)

Goal: Add hot reload, event handling, and state management.

Tasks:

  1. Implement file watcher for hot reload (notify crate)
  2. Add input handling system (focus management, key bindings)
  3. Create action dispatcher (button clicks → app state changes)
  4. Add more component types (Input, List, Table, Panel)
  5. Build example app (simple file browser or todo list)

New dependencies:

notify = "6.0"
tokio = { version = "1", features = ["full"] }

Deliverable: Functional TUI app with editable layout.

Phase 3: Advanced Capabilities (4 weeks)

Goal: State machine routing, prop binding, and component library.

Tasks:

  1. Implement state machine router (AppState enum + transitions)
  2. Add template interpolation ("{{user.name}}" → actual data)
  3. Build reusable component library (10+ widget types)
  4. Create visual layout editor (TUI for editing JSON)
  5. Write comprehensive documentation + examples

Deliverable: Production-ready framework for JSON-driven TUIs.


Crates

Core TUI:

State Management:

Configuration:

File Watching:

Alternative TUI Frameworks:

Documentation

Example Projects

Production TUIs in Rust:

Architecture References:


Practical Examples

Example 1: Basic Enum-Based Component

use ratatui::{
    backend::CrosstermBackend,
    layout::Rect,
    widgets::{Block, Borders, Paragraph},
    Frame, Terminal,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize)]
enum SimpleComponent {
    Text { content: String },
    Box { title: String, child: Box<SimpleComponent> },
}

impl SimpleComponent {
    fn render(&self, f: &mut Frame, area: Rect) {
        match self {
            SimpleComponent::Text { content } => {
                let widget = Paragraph::new(content.as_str());
                f.render_widget(widget, area);
            }
            SimpleComponent::Box { title, child } => {
                let block = Block::default()
                    .title(title.as_str())
                    .borders(Borders::ALL);
                let inner = block.inner(area);
                f.render_widget(block, area);
                child.render(f, inner);
            }
        }
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load from JSON
    let json = r#"
    {
        "Box": {
            "title": "Welcome",
            "child": {
                "Text": {
                    "content": "Hello from Rust TUI!"
                }
            }
        }
    }
    "#;
    let component: SimpleComponent = serde_json::from_str(json)?;
    
    // Setup terminal
    let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
    terminal.clear()?;
    
    // Render
    terminal.draw(|f| {
        component.render(f, f.size());
    })?;
    
    std::thread::sleep(std::time::Duration::from_secs(3));
    Ok(())
}

Example 2: JSON Config with Hot Reload

use notify::{Watcher, RecursiveMode, watcher, DebouncedEvent};
use std::sync::mpsc::channel;
use std::time::Duration;

struct HotReloadApp {
    component: SimpleComponent,
    config_path: PathBuf,
}

impl HotReloadApp {
    fn new(config_path: PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
        let component = Self::load_config(&config_path)?;
        Ok(Self { component, config_path })
    }
    
    fn load_config(path: &Path) -> Result<SimpleComponent, Box<dyn std::error::Error>> {
        let json = std::fs::read_to_string(path)?;
        Ok(serde_json::from_str(&json)?)
    }
    
    fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
        
        let (tx, rx) = channel();
        let mut watcher = watcher(tx, Duration::from_millis(500))?;
        watcher.watch(&self.config_path, RecursiveMode::NonRecursive)?;
        
        loop {
            // Render current component
            terminal.draw(|f| {
                self.component.render(f, f.size());
            })?;
            
            // Check for config file changes
            if let Ok(DebouncedEvent::Write(_)) = rx.try_recv() {
                match Self::load_config(&self.config_path) {
                    Ok(new_component) => {
                        self.component = new_component;
                        terminal.clear()?;
                    }
                    Err(e) => eprintln!("Failed to reload config: {}", e),
                }
            }
            
            // Handle input (q to quit)
            if crossterm::event::poll(Duration::from_millis(100))? {
                if let crossterm::event::Event::Key(key) = crossterm::event::read()? {
                    if key.code == crossterm::event::KeyCode::Char('q') {
                        break;
                    }
                }
            }
        }
        
        Ok(())
    }
}

Example 3: State Machine Integration

#[derive(Debug, Clone, PartialEq)]
enum AppScreen {
    MainMenu,
    FileExplorer { current_dir: PathBuf },
    Editor { file: PathBuf },
}

struct AppStateMachine {
    current: AppScreen,
    layout_for_screen: HashMap<AppScreen, SimpleComponent>,
}

impl AppStateMachine {
    fn new() -> Self {
        let mut layouts = HashMap::new();
        
        // Define layouts for each screen
        layouts.insert(
            AppScreen::MainMenu,
            SimpleComponent::Box {
                title: "Main Menu".to_string(),
                child: Box::new(SimpleComponent::Text {
                    content: "Press F to open file explorer".to_string(),
                }),
            },
        );
        
        Self {
            current: AppScreen::MainMenu,
            layout_for_screen: layouts,
        }
    }
    
    fn handle_input(&mut self, key: crossterm::event::KeyCode) {
        match (&self.current, key) {
            (AppScreen::MainMenu, crossterm::event::KeyCode::Char('f')) => {
                self.current = AppScreen::FileExplorer {
                    current_dir: std::env::current_dir().unwrap(),
                };
            }
            (AppScreen::FileExplorer { .. }, crossterm::event::KeyCode::Esc) => {
                self.current = AppScreen::MainMenu;
            }
            _ => {}
        }
    }
    
    fn current_layout(&self) -> &SimpleComponent {
        self.layout_for_screen.get(&self.current)
            .expect("Layout not defined for current screen")
    }
}

Decision Framework

Use this flowchart to choose your architecture:

START: What's your primary constraint?


├─ Need hot-reload during development?
│  └─ YES → JSON-Driven Components (enum dispatch)
│      └─ NO → Continue

├─ Complex state management?
│  └─ YES → Component Tree (React-like) or Event-Driven Widgets
│      └─ NO → Continue

├─ Animations or game-like interactions?
│  └─ YES → Immediate Mode
│      └─ NO → Continue

├─ Modal workflow (wizard, multi-step process)?
│  └─ YES → State Machine Routing
│      └─ NO → Continue

├─ Need precise control over every cell?
│  └─ YES → Grid/Table Layout (raw terminal access)
│      └─ NO → Continue

├─ Building standard UI (forms, lists, tables)?
│  └─ YES → Constraint-Based (ratatui recommended)
│      └─ NO → Reassess requirements

DEFAULT: Start with Constraint-Based (ratatui)
         Add patterns incrementally as needed

Combination strategy:


Anti-Patterns to Avoid

1. Over-Engineering State Management

Bad:

// Complex state with deeply nested mutations
struct AppState {
    screens: HashMap<ScreenId, Screen>,
    navigation_stack: Vec<ScreenId>,
    undo_history: Vec<AppState>,
    // ... 20 more fields
}

Good:

// Simple, flat state
struct AppState {
    current_screen: Screen,
    data: HashMap<String, String>,
}

Lesson: Start simple. Add structure only when complexity demands it.

2. Ignoring Terminal Resize

Bad:

// Hardcoded layout that breaks on resize
let sidebar = Rect::new(0, 0, 30, 100);
let main = Rect::new(30, 0, 50, 100);

Good:

// Responds to actual terminal size
let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
    .split(f.size());

3. Blocking the UI Thread

Bad:

fn render(f: &mut Frame) {
    // Blocks for 5 seconds!
    let data = fetch_data_from_api();
    render_data(f, data);
}

Good:

// Async fetch with loading state
enum DataState {
    Loading,
    Loaded(Data),
    Error(String),
}

fn render(f: &mut Frame, state: &DataState) {
    match state {
        DataState::Loading => render_spinner(f),
        DataState::Loaded(data) => render_data(f, data),
        DataState::Error(msg) => render_error(f, msg),
    }
}

4. Not Testing Layout Math

Bad:

// Assumed 80x24 terminal
let rows = 24;
let cols = 80;

Good:

// Test with various terminal sizes
#[cfg(test)]
mod tests {
    #[test]
    fn layout_works_on_small_terminal() {
        let area = Rect::new(0, 0, 40, 10);
        let chunks = split_layout(area);
        assert!(chunks[0].width > 0);
    }
}

Next Steps

Quick Wins (1-2 hours each)

  1. JSON Hello World:

    • Create layout.json with Text + Box components
    • Render with ratatui
    • Verify hot reload works
  2. Constraint Experiment:

    • Build 3-pane layout (header, content, footer)
    • Test with different terminal sizes
    • Observe constraint behavior
  3. Component Library:

    • Implement 3 new component types (List, Table, Input)
    • Test serialization round-trip (Rust → JSON → Rust)

Weekend Projects (8-16 hours)

  1. File Browser TUI:

    • Master-detail layout
    • Directory tree in sidebar
    • File preview in main pane
    • Keyboard navigation
  2. Terminal Dashboard:

    • Multiple panels with system info
    • Auto-refresh data
    • JSON-configurable panel layout
  3. TUI Chat Client:

    • Message list (scrollable)
    • Input box at bottom
    • Real-time updates (async)

Long-Term Goals (1-3 months)

  1. Visual Layout Editor:

    • TUI app for editing layout JSON
    • Drag-and-drop component arrangement
    • Live preview of changes
  2. Component Library:

    • 20+ reusable widgets
    • Published as crate
    • Documentation + examples
  3. Production App:

    • Full-featured application (log viewer, task manager, etc.)
    • Persistent state
    • Comprehensive testing
    • Distribution (binary releases)

Closing Thoughts

The TUI landscape in Rust is mature and production-ready. ratatui provides excellent constraint-based layouts, serde enables JSON-driven UIs, and crossterm handles cross-platform terminal quirks. The patterns discussed here—from simple enum dispatch to complex state machines—give you a flexible toolkit for any terminal UI challenge.

Start with ratatui’s constraint system, add JSON-driven components for flexibility, and introduce state machine routing only when workflows demand it. Build incrementally, test on various terminal sizes, and enjoy the rapid feedback loop of hot-reloading layouts.

The terminal isn’t a limitation—it’s a feature. Character-based rendering forces clarity, keyboard-first interaction demands thoughtful UX, and cross-platform support is built-in. Your Rust TUI can be as sophisticated as any GUI, with the added benefits of SSH-ability, scriptability, and a 40-year-old rendering substrate that just works.


Generated: 2025-10-17
Maintainer: Synthesized from conversation archaeology
License: CC0 (Public Domain)
Feedback: Update via continued exploration and pattern refinement

═══════════════════════════════════════════════════════════════
 sysop::boring.core - float.bbs viewer v0.1
═══════════════════════════════════════════════════════════════
    
▓▓▒▒ TODAY: 2025-10-27 ▒▒▓▓

<< 2025-10-26 | 2025-10-28 >>

🎯 What Evan Needs This Morning

Pending PRs (Awaiting Review/Merge)

  • PR #604: GP node assessment + basket automation

    • Status: In approval backlog, merge conflicts being resolved
    • Demos: Successfully demo’d on 2025-10-24 sprint demo (Daniel happy with feedback)
    • Next: Check if conflicts resolved, ready for merge to staging
  • PR #606: [Description needed - check GitHub]

    • Status: Demo’d successfully, awaiting review
    • Next: Check GitHub status
  • PR #607: [Description needed - check GitHub]

    • Status: Demo’d successfully, awaiting review
    • Next: Check GitHub status

Active Issues Ready for Dev

  • Issue #122: Assessment workflow

    • Location: /Users/evan/float-hub/rangle/issues/active/122-assessment-workflow.md
    • Status: Fully documented with acceptance criteria
    • Priority: Assessment UX experience (per Scott sync 2025-10-24)
    • Key consideration: Guest→account response transfer (piggybacking basket logic)
  • Issue #442: HEIC upload support

    • Location: /Users/evan/float-hub/rangle/issues/active/442-heic-upload-support.md
    • Status: Fully documented with acceptance criteria
    • Priority: Lower than #122 (per Scott sync realignment)

Follow-ups from Weekend

  • Check GitHub PR statuses (#604, #606, #607) - are they merged? ready for staging?
  • Scott mentioned creating UI/UX ticket (priority 3) and multi-product assessment response logging ticket
  • Wins tracking system now operational - remember to capture wins as they happen

First Tasks

  • Check pharmacy-online PR status (merged? staging? conflicts?)
  • Review Issue #122 (assessment workflow) - priority work
  • Check if Scott’s new tickets created (UI/UX, multi-product logging)
  • Capture wins as work happens (two-home system: quick log + weekly review)

Context from Yesterday

Weekend mode: Shack building + infrastructure work

  • float.bbs viewer operational
  • TodayDrawer component shipped
  • Documentation preserved
  • Monday prep notes ready

Repo: https://github.com/pharmonline/pharmacy-online Local: ~/projects/pharmacy-online


timelog

  • 11:45pm - 12:03am - [project::float-bbs-viewer] hermit crab blueprints → forge patterns extracted (102KB doc)
  • 11:40pm - 11:45pm - [project::float-infrastructure] domain migration → sysop-beta.floatbbs.net live
  • 12:03am - 12:05am - [project::float-hub] CLAUDE.md evna integration → explicit tool names + capture triggers

Late Night: Infrastructure & Blueprinting

float-bbs-viewer Architecture Extraction (11:45pm - 12:03am)

  • Extracted patterns from float-dispatch-manifesto-forge (React/Vite/ShadCN)
  • Created hermit crab reference: 2025-10-26-dispatch-blueprints-for-bbs-viewer-hermit-crab-patterns.md
  • Key patterns: color-coded imprint system, grid layouts, Tailwind HSL tokens, editorial philosophy sections
  • Translation map: React hooks → Astro content collections, SPA routing → SSG file-based
  • Breadcrumb: /Users/evan/projects/float-bbs-viewer/2025-10-26-dispatch-blueprints-for-bbs-viewer-hermit-crab-patterns.md

CLAUDE.md evna Integration (12:03am - 12:05am)

  • Replaced vague “evna-context-concierge” references with explicit tool names
  • Added mandatory capture triggers (7-item checklist: after features, docs, infrastructure, archaeology, context switches, obstacles, chunks)
  • Context capture pattern template (ctx::, project::, format)
  • Breadcrumb: /Users/evan/float-hub/CLAUDE.md:308-315, 374-403, 422

Morning: Brain Booting

(Space for morning thoughts)

Press ESC or Ctrl+D to close