esync

Directory watching and remote syncing
Log | Files | Refs | README | LICENSE

2026-03-18-edit-config-design.md (5675B)


      1 # Edit Config from TUI
      2 
      3 ## Summary
      4 
      5 Add an `e` key to the TUI dashboard that opens `.esync.toml` in `$EDITOR`. On save, the config is reloaded and the watcher/syncer are rebuilt with the new values. If no config file exists, a template is created; if the user exits without saving, nothing is persisted.
      6 
      7 Also renames the project-level config from `esync.toml` to `.esync.toml` (dotfile).
      8 
      9 ## Config File Rename
     10 
     11 - `FindConfigFile()` in `internal/config/config.go` searches `./.esync.toml` instead of `./esync.toml`
     12 - System-level paths (`~/.config/esync/config.toml`, `/etc/esync/config.toml`) remain unchanged
     13 - `cmd/init.go` default output path changes from `./esync.toml` to `./.esync.toml`
     14 - `esync.toml.example` renamed to `.esync.toml.example`
     15 - `README.md` references updated from `esync.toml` to `.esync.toml`
     16 - The `e` key always targets `./.esync.toml` in cwd
     17 
     18 ## Key Flow
     19 
     20 ### 1. Keypress
     21 
     22 Dashboard handles `e` keypress in `updateNormal()` and sends `EditConfigMsg{}` up to `AppModel`. Only active in dashboard view (not logs). Already gated by `updateNormal` vs `updateFiltering` dispatch, so typing `e` during filter input is safe.
     23 
     24 ### 2. AppModel receives EditConfigMsg
     25 
     26 - Target path: `./.esync.toml` (cwd)
     27 - **File exists:** record SHA-256 checksum, open in editor via `tea.ExecProcess`
     28 - **File does not exist:** write template to a temp file (with `.toml` suffix for syntax highlighting, e.g. `os.CreateTemp("", "esync-*.toml")`), record its checksum, open temp file in editor
     29 - **Editor resolution:** check `$VISUAL`, then `$EDITOR`, fall back to `vi`
     30 
     31 ### 3. Editor exits (editorConfigFinishedMsg)
     32 
     33 - **New file flow (was temp):** compare checksums. If unchanged, delete temp file, done. If changed, copy temp contents to `./.esync.toml`, delete temp file.
     34 - **Existing file flow:** compare checksums. If unchanged, done.
     35 - **Config changed:** parse with `config.Load()`.
     36   - **Parse fails:** push error to TUI status line (e.g., "config error: missing sync.remote"), keep old config running.
     37   - **Parse succeeds:** send new `*config.Config` on `configReloadCh` channel.
     38 
     39 Note: `tea.ExecProcess` blocks the TUI program, so the user cannot press `e` again while the editor is open. This makes the capacity-1 channel safe without needing non-blocking sends.
     40 
     41 ### 4. cmd/sync.go handles reload
     42 
     43 Listens on `app.ConfigReloadChan()`:
     44 
     45 1. Stop existing watcher (blocks until `<-w.done`, ensuring no in-flight handler)
     46 2. Wait for any in-flight sync to complete before proceeding
     47 3. Rebuild syncer with new config
     48 4. Create new watcher with new config values (local path, debounce, ignore patterns, includes)
     49 5. Create new sync handler closure capturing the new syncer
     50 6. Push status event: "config reloaded"
     51 
     52 ### 5. Flag-only mode (--local/--remote without config file)
     53 
     54 When esync was started with `--local`/`--remote` flags and no config file, pressing `e` still opens `./.esync.toml`. If the file doesn't exist, the template is shown. After save, the reloaded config replaces the flag-derived config entirely (CLI flags are not re-applied on top). This lets users transition from quick flag-based usage to a persistent config file.
     55 
     56 ## Template Content
     57 
     58 A new `EditTemplateTOML()` function in `internal/config/config.go`, separate from the existing `DefaultTOML()` used by `esync init`. The edit template is minimal with most options commented out, while `DefaultTOML()` remains unchanged for `esync init`'s string-replacement logic.
     59 
     60 ```toml
     61 # esync configuration
     62 # Docs: https://github.com/LouLouLibs/esync
     63 
     64 [sync]
     65 local = "."
     66 remote = "user@host:/path/to/dest"
     67 # interval = 1  # seconds between syncs
     68 
     69 # [sync.ssh]
     70 # key = "~/.ssh/id_ed25519"
     71 # port = 22
     72 
     73 [settings]
     74 # watcher_debounce = 500   # ms
     75 # initial_sync = false
     76 # include = ["src/", "cmd/"]
     77 # ignore = [".git", "*.tmp"]
     78 
     79 # [settings.rsync]
     80 # archive = true
     81 # compress = true
     82 # delete = false
     83 # copy_links = false
     84 # extra_args = ["--exclude=.DS_Store"]
     85 
     86 # [settings.log]
     87 # file = "esync.log"
     88 # format = "text"
     89 ```
     90 
     91 ## Help Bar
     92 
     93 Updated dashboard help line:
     94 
     95 ```
     96 q quit  p pause  r resync  ↑↓ navigate  enter expand  v view  e config  l logs  / filter
     97 ```
     98 
     99 `e config` inserted between `v view` and `l logs`, using existing `helpKey()`/`helpDesc()` styling.
    100 
    101 ## New Types and Channels
    102 
    103 | Item | Location | Purpose |
    104 |------|----------|---------|
    105 | `EditConfigMsg` | `internal/tui/app.go` | Dashboard → AppModel signal |
    106 | `editorConfigFinishedMsg` | `internal/tui/app.go` | Editor exit result (distinct from existing `editorFinishedMsg`) |
    107 | `configReloadCh` | `AppModel` field | `chan *config.Config`, capacity 1 |
    108 | `ConfigReloadChan()` | `AppModel` method | Exposes channel to `cmd/sync.go` |
    109 
    110 ## Error Handling
    111 
    112 - Bad TOML or missing required fields: status line error, old config retained
    113 - Editor not set: check `$VISUAL` → `$EDITOR` → `vi`
    114 - Editor returns non-zero exit: treat as "no change", discard
    115 - Watcher detects `.esync.toml` write: harmless (rsync transfers the small file). Not added to default ignore since users may intentionally sync config files.
    116 
    117 ## Files Modified
    118 
    119 - `internal/config/config.go` — rename `esync.toml` → `.esync.toml` in `FindConfigFile()`, add `EditTemplateTOML()`
    120 - `internal/tui/app.go` — new message types, `configReloadCh`, editor launch/return handling
    121 - `internal/tui/dashboard.go` — `e` key binding, help line update
    122 - `cmd/sync.go` — listen on `configReloadCh`, tear down and rebuild watcher/syncer
    123 - `cmd/init.go` — update default output path to `./.esync.toml`, update `defaultIgnorePatterns`
    124 - `esync.toml.example` — rename to `.esync.toml.example`
    125 - `README.md` — update references from `esync.toml` to `.esync.toml`