flower
A structural TUI editor for config files, built on fig.
Where a text editor edits characters, flower edits the tree. You navigate the parsed config structurally — into mappings, along sequences, down to scalars — and edit one value at a time. Every change is a path-addressed, lossless splice through fig's editor: the bytes you didn't touch (comments, key order, blank lines, quoting) stay byte-for-byte identical, and the document is only ever committed in a valid state.
flower is to fig what bough is to twig: the structural editor over the
lossless AST. (leaf is the rich-text sibling — the right model for permissive
document formats, the wrong one for strict config grammars, where free-text
editing spends most of its keystrokes in states the parser refuses to hold.)
Status
Early prototype. Working today:
- Open a file (format detected from extension), render its structure as an indented, type-colored tree.
- Navigate structurally: move between siblings, into children, out to parents; expand/collapse containers.
- Edit a scalar in place (typed:
true/42/3.14/null/text) — committed viafig::Editor::replace_value, so the splice is lossless and validated. - Delete a mapping entry or sequence item.
- Save (writes fig's edited source back to disk).
Deliberately not here yet — see the roadmap.
Keys
| Key | Action |
|---|---|
j / k (or ↓/↑) |
next / previous row |
l (or →) |
expand container, or step into first child |
h (or ←) |
collapse container, or step out to parent |
Enter / Space |
toggle a container / edit a scalar |
e |
edit the selected scalar |
x |
delete the selected entry or item |
s |
save to disk |
q |
quit |
In edit mode: type to change the value, Enter to commit, Esc to cancel.
Usage
Supported formats: JSON/JSONC/JSON5, YAML, TOML, ZON, and the fig dialect
(.fig/.figl) — anything fig's default feature set parses.
Architecture
A Cargo workspace, split so the editing logic is frontend-neutral (mirroring
leaf-core / leaf-ratatui / leaf-tui):
fig (Zig) → fig-sys (FFI, libfig.a) → fig crate (Editor/Document/Value)
│
crates/flower-core (the model — no UI, no fs)
┌────────────────────────┼─────────────────────────┐
crates/flower-ratatui crates/flower-ffi crates/flower-tui
(ratatui widget) (UniFFI C-ABI binding) (app: file I/O + loop)
│
packages/flower-swift
(FlowerFFI + FlowerUI, SwiftUI)
│
apps/flower-editor
(macOS/iOS example app)
The tiers
| Tier | Path | What it is |
|---|---|---|
| core | crates/flower-core |
the frontend-neutral model — the navigable Row tree + path-addressed lossless edits over fig. No UI, no fs. |
| widget | crates/flower-ratatui |
a draw(frame, &Model, header) ratatui widget. |
| binding | crates/flower-ffi |
the UniFFI C-ABI binding — wraps the filesystem-free Model so a native Apple app can drive it. The native-Apple peer of the ratatui widget. |
| app | crates/flower-tui |
the terminal app (binary flower) — file I/O + event loop. |
| Swift SDK | packages/flower-swift |
the Swift Package — FlowerUI, a SwiftUI structural tree editor, over the UniFFI flower-ffi binding. |
| Swift app | apps/flower-editor |
the cross-platform (macOS + iOS) SwiftUI example, consuming packages/flower-swift. |
The Swift frontend keeps the same contract as the TUI: core owns the model
(the tree, selection, and every lossless edit), the frontend only renders the
visible rows and forwards navigation / edit intents by row index. Every call
across the FFI returns a DocView — the flat visible-row list plus selection,
dirty, and status — one crossing that both mutates and repaints.
flower-ffi builds today for the aarch64-apple-darwin slice (fig-sys ships a
prebuilt macOS-arm64 static lib); the other Apple slices (macOS-x64, iOS,
iOS-sim) build fig from source via Zig cross-compiling and are the next step for
a distributable FlowerFFI.xcframework.
crates/flower-core— the frontend-neutral model. Depends only onfigandstd.format.rs— file extension →fig::Format.tree.rs— flattens afig::Valueinto navigableRows, each carrying itsfigpath (aVec<Seg>ofKey/Index) — exactly whatfig::Editorops take — honoring a collapsed-set.model.rs—Model: owns thefig::Editor(source of truth), the derivedValue/rows, selection, and the edit ops. Constructed from bytes; the embedder owns the file.
crates/flower-ratatui— adraw(frame, &Model, header)widget. Depends onflower-core+ratatui.crates/flower-tui— the terminal app (binaryflower): reads the file, runs the event loop, writes on save. Depends on both.
The read path is fig::Document::to_value() (a semantic Value tree); the write
path is fig::Editor's path-addressed ops. After every edit the model re-derives
the tree from Editor::source(), so the editor's owned source is always
canonical.
The commit-sink Backend trait
flower-core::Model is generic over a Backend — it never touches a concrete
editor. It builds path-addressed EditOps, applies them through the backend,
and reads the tree back via Backend::to_value:
FigBackend(in flower-core) drives a rawfig::Editor— a standalone config file.- A prov backend (
ProvBackend, in theprovuirepo) drives the metadata region of a prov document through prov's carrier-awareMetaEditor, leaving the prose body untouched. That composition (flower for metadata + leaf for the body, over one prov document) is proven by a headless test in provui.
flower-core stays config-generic and prov stays consumer-agnostic, so the app-specific bridge lives in provui, not here — flower doesn't depend on prov.
Roadmap
- Value-editing affordances: type-aware widgets (bool toggle, enum picker, number stepper) instead of one free-text field; today's edit coerces by literal shape, which a schema layer would fix.
- Insert: add keys / append sequence items (
figalready exposes the ops). - Reorder / move:
move_key,reorder_keys,move_item. - Comments: show and edit leading/trailing comments (
fig::Editorexposesleading_comment/set_trailing_comment/…). - Schema layer: the big one — fig has none, so a "what keys/values are valid here" layer is ours to add; unlocks completion, typed widgets, validation.
- Native frontend affordances (
FlowerUI): today it edits scalars in one inline text field. Next: type-aware widgets (bool toggle, number stepper, enum picker), keyboard navigation, insert/reorder, and comment display — the same roadmap the TUI has, in SwiftUI. - The rest of the Apple slices: cross-compile
figvia Zig for macOS-x64, iOS, and the simulator soscripts/build-xcframework.shproduces a fullFlowerFFI.xcframework.