Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ repository.

## Prerequisites

- **Go 1.24+** - the module declares `go 1.24` and CI runs on 1.24.
- **Go** - the version declared in `go.mod` (CI builds with the same
version).
- **tmux 3.1+** - required at runtime and for the integration test suite.
`mox` uses tmux 3.1 features such as `split-window -l <n>%`.
- **golangci-lint** - used by `make lint` and by CI. Install from
Expand Down
8 changes: 5 additions & 3 deletions internal/cli/editor_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,15 @@ func (st *editorState) applyDraft(d *sessionDraft) error {

sessMap := findOrCreateMapKey(st.root.Content[0], "sessions")

// Guard against name collisions (I1: prevents duplicate keys in the map).
// A new or renamed session must not land on an existing key: yaml.v3
// refuses to decode a mapping with duplicate keys.
if !d.deleted && (d.orig == "" || d.name != d.orig) {
if findMapKey(sessMap, d.name) != nil {
return fmt.Errorf("session %q already exists", d.name)
}
}

// Snapshot the Content slice for rollback on write failure (C1).
// Snapshot Content so a failed write can roll the node tree back.
snapshot := make([]*yaml.Node, len(sessMap.Content))
copy(snapshot, sessMap.Content)
var didRename bool
Expand All @@ -171,7 +172,8 @@ func (st *editorState) applyDraft(d *sessionDraft) error {
case d.deleted:
removeMapKey(sessMap, d.orig)
default:
// M1: pure rename — only rename the key, don't re-encode the session body.
// Pure rename: rename the key only, so the body keeps its original
// formatting and comments instead of being re-encoded.
if d.orig != "" && d.name != d.orig && sameSessionYAML(d.sess, st.cfg.Sessions[d.orig]) {
didRename = true
origName = d.orig
Expand Down
8 changes: 4 additions & 4 deletions internal/cli/editor_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func TestApplyDraftStaleBlocksWrite(t *testing.T) {
}
}

// Regression test for C1: rollback on failed writes.
// A failed write must not leave a half-applied rename in the node tree.
func TestApplyDraftRollbackOnWriteFailure(t *testing.T) {
st := testEditorState(t, editorFixtureYAML)
d := newDraft(st.cfg, "webfarm")
Expand Down Expand Up @@ -247,7 +247,7 @@ func TestApplyDraftRollbackOnWriteFailure(t *testing.T) {
}
}

// Regression test for I1: collision guard.
// Renames and added sessions must not collide with an existing name.
func TestApplyDraftRejectsDuplicateNames(t *testing.T) {
st := testEditorState(t, editorFixtureYAML)

Expand Down Expand Up @@ -281,7 +281,7 @@ func TestApplyDraftRejectsDuplicateNames(t *testing.T) {
}
}

// Regression test for I3: draft mutations don't leak into st.cfg.
// After a save, the draft and the typed config must not share memory.
func TestApplyDraftNoAliasAfterSave(t *testing.T) {
st := testEditorState(t, editorFixtureYAML)
d := newDraft(st.cfg, "webfarm")
Expand All @@ -308,7 +308,7 @@ func TestApplyDraftNoAliasAfterSave(t *testing.T) {
}
}

// Regression test for M1: pure rename preserves flow-style and comments.
// A pure rename keeps the session body's flow style and comments.
func TestApplyDraftPureRenamePreservesFormat(t *testing.T) {
st := testEditorState(t, editorFixtureYAML)

Expand Down
28 changes: 0 additions & 28 deletions internal/tmux/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,19 +198,6 @@ func (c *Client) SelectWindowByID(windowID string) error {
return err
}

// BaseIndex returns the global base-index option (default 0).
func (c *Client) BaseIndex() (int, error) {
out, err := c.Run("show-options", "-gv", "base-index")
if err != nil {
return 0, nil
}
idx, err := strconv.Atoi(strings.TrimSpace(out))
if err != nil {
return 0, nil
}
return idx, nil
}

// SetPaneTitle sets the title of the target pane (visible in tmux's
// pane-border-format if configured to show #{pane_title}).
func (c *Client) SetPaneTitle(target, title string) error {
Expand Down Expand Up @@ -252,21 +239,6 @@ func (c *Client) SelectLayout(windowTarget, layoutName string) error {
return err
}

// NewWindowInSession creates a new window in the named session and returns
// the new window's id. The session must already exist. Unlike CreateWindow,
// the new window is selected (active) immediately.
func (c *Client) NewWindowInSession(session, name, startDir string) (string, error) {
args := []string{"new-window", "-t", "=" + session, "-P", "-F", "#{window_id}", "-n", name}
if startDir != "" {
args = append(args, "-c", startDir)
}
out, err := c.Run(args...)
if err != nil {
return "", err
}
return strings.TrimSpace(out), nil
}

// WindowInfo describes a single window for inspection / import.
type WindowInfo struct {
ID string // tmux window id (@N)
Expand Down
Loading