fedit

how it works

An Elm-style update loop. Pure model, pure update, one effect interpreter that folds its results back into the loop as messages.

The Model is pure data. Editor.update is a pure function that takes a Msg and returns a new model plus a list of effects. runEffect is the only impure code path — it does file I/O and folds its result back into the loop as another Msg. Layout.render projects the model to a Screen, and Renderer.render writes that grid as ANSI escapes.

data flow
                         +----------------------+
                        |        Model         |
                        |  workspace . tree    |
                        |  buffers + cursors   |
                        |  focus, theme,       |
                        |  panels, terminal    |
                        +-----+----------+-----+
                              |          |
              read by         |          |     read by
        +---------------------+          +---------------------+
        |                                                      |
        v                                                      v
+-----------------+                                  +-------------------+
|  Editor.update  |                                  |   Layout.render   |
|     (pure)      |                                  |      (pure)       |
+--------+--------+                                  +---------+---------+
         |                                                     |
   (model', effects)                                        Screen
         |                                                     |
         v                                                     v
+-----------------+                                  +-------------------+
|    runEffect    |                                  |  Renderer.render  |
|    (impure)     |                                  |   ANSI escapes    |
|  ScanWorkspace  |                                  +---------+---------+
|  LoadFile       |                                            |
|  SaveBuffer     |                                            v
+--------+--------+                                     terminal output
         |
         v
        Msg  ----------> dispatch <---------- KeyPressed / Resize
                            ^                       (main loop)
                            |
                       feeds back 

buffer storage

Text buffers use a piece table. The original file contents stay in one string, inserted text is appended to another string, and the visible document is represented as a list of pieces. Inserts and deletes stay local to the piece list while preserving enough state for undo and redo snapshots.

files and encoding

Files are read as UTF-8. The line ending of the loaded file (LF or CRLF) is detected and reused on save; the buffer always works in \n form internally. Saving writes UTF-8 without a byte-order mark.

concurrency

The workspace scan, file open, file save, clipboard, and config writes all run on the thread pool via Task.Run. The main loop drains a ConcurrentQueue<Msg> of completed effects each tick, so input never blocks behind I/O. ScanWorkspace and LoadFile each carry a single CancellationTokenSource so a second invocation drops the previous result.

further reading

The full architecture overview, including module dependency order and the Msg / Effect taxonomy, lives in CLAUDE.md in the repo. Phase notes for shipped work are in CHANGELOG.md.