Architecture
micasa is a Bubble Tea application following The Elm Architecture (TEA): Model, Update, View.
Package layout#
cmd/micasa/ CLI entry point (kong argument parsing)
internal/
app/ Bubble Tea application layer
model.go Model struct, Init, Update, key dispatch
types.go Mode, Tab, cell, columnSpec, etc.
handlers.go TabHandler interface + entity implementations
tables.go Column specs, row builders, table construction
forms.go Form builders, validators, submit logic
styles.go Wong colorblind-safe palette, all lipgloss styles
view.go Main View() assembly, overlays
table.go Table rendering (headers, rows, viewport)
collapse.go Hidden column badges
house.go House profile rendering
dashboard.go Dashboard data loading + view
sort.go Multi-column sort logic
undo.go Undo/redo stack
form_select.go Select field ordinal jumping
calendar.go Inline date picker overlay
column_finder.go Fuzzy column jump overlay
data/ Data access layer
models.go GORM models (HouseProfile, Project, Document, etc.)
store.go Store struct, CRUD methods, queries
doccache.go Document BLOB extraction + XDG cache
dashboard.go Dashboard-specific queries
path.go DB path resolution (XDG)
validation.go Parsing helpers (dates, money, ints)
Key design decisions#
TabHandler interface#
Entity-specific operations (load, delete, add form, edit form, inline edit,
submit, snapshot, etc.) are encapsulated in the TabHandler interface.
Each entity type (projects, quotes, maintenance, appliances, vendors,
documents) implements this interface as a stateless struct.
This eliminates scattered switch tab.Kind dispatch. Adding a new entity type
means implementing one interface – no shotgun surgery across the codebase.
Detail views (service log, appliance maintenance) also implement TabHandler,
so they get all the same capabilities (add, edit, delete, sort, undo) for
free.
Modal key handling#
micasa uses three modes: Nav, Edit, and Form. The key dispatch chain in
Update() is:
- Window resize handling
ctrl+qalways quitsctrl+ccancels in-flight LLM operations- Chat chunk messages (streaming responses)
- Help overlay intercepts
esc/?when open - Chat overlay: absorbs all keys when open
- Note preview overlay: any key dismisses
- Calendar date picker: absorbs all keys when open
- Column finder overlay: absorbs all keys when open
- Inline input: absorbs keys when editing a cell
- Form mode delegates to
huhform library - Dashboard intercepts nav keys when visible
- Common keys (shared by Nav and Edit)
- Mode-specific keys
The bubbles/table widget has its own vim keybindings. In Edit mode, d and
u are stripped from the table’s KeyMap so they can be used for delete/undo
without conflicting with half-page navigation.
Effective tab#
The effectiveTab() method returns the detail tab when a detail view is open,
or the main active tab otherwise. All interaction code uses this method, so
detail views work identically to top-level tabs.
Cell-based rendering#
Table cells carry type information (cellKind): text, money, date, status,
readonly, drill. The renderer uses this to apply per-kind styling (green
for money, colored for status, accent for drill). Sort comparators are
also kind-aware.
Colorblind-safe palette#
All colors use the Wong palette with lipgloss.AdaptiveColor{Light, Dark}
variants, so the UI works on both dark and light terminal backgrounds. Color
roles are defined in styles.go.
Data flow#
User keystroke
-> tea.KeyMsg
-> Model.Update()
-> key dispatch (mode-aware)
-> data mutation (Store CRUD)
-> reloadAfterMutation() (refreshes effective tab, marks others stale)
-> Model.View()
-> rendered string to terminal
All data mutations go through the Store, which uses GORM for SQLite access.
After any mutation, reloadAfterMutation() refreshes the effective tab and
marks all other tabs as stale. Stale tabs are lazily reloaded when navigated
to. The dashboard is refreshed when it becomes visible.
Overlays#
Dashboard, help, calendar, column finder, and note preview are rendered as overlays using bubbletea-overlay. They composite on top of the live table view with dimmed backgrounds. Overlays can stack (e.g. help on top of dashboard).