diff --git a/docs/changes/unreleased/1629-home-task-target.md b/docs/changes/unreleased/1629-home-task-target.md new file mode 100644 index 000000000..ce91397dd --- /dev/null +++ b/docs/changes/unreleased/1629-home-task-target.md @@ -0,0 +1,8 @@ +--- +kind: fixed +title: Home tasks keep the project displayed before submission +pr: 1629 +surface: [chat] +invalidates: + - "A task typed on Home could follow another project's newer conversation after the command cleared its draft. Slash commands now capture the displayed project before rebuilding Home, sharing the ordinary message opening path." +--- diff --git a/internal/manual/chat/home.md b/internal/manual/chat/home.md index f8a214b47..8d5e67a27 100644 --- a/internal/manual/chat/home.md +++ b/internal/manual/chat/home.md @@ -1345,6 +1345,11 @@ one behind your back. This is every fate, in the words the drop-up draws them in | **`a fresh conversation behind home`** | `/new` `/clear` `/clean` `/reset` | Replaces the conversation behind the screen and says `started a fresh conversation behind home`. It is not the same act as `enter`, which opens a conversation at the target. | | **`closes the conversation behind home`** | `/quit` `/exit` `/q` | Closes it and says `closed · `. When it was the last conversation this terminal was holding, codeaf leaves. | +**A task typed on home uses the project shown before you press enter.** Clearing the +command from the box does not retarget it to a newer conversation in another project. +An ordinary message and `/task ` open in that same displayed project, even when +another project's engine is still running. + **The fate is never the half that gets cut.** On a narrow window the command's own description gives way first, whole, and what `enter` will do stays on the row. diff --git a/internal/tui3/home.go b/internal/tui3/home.go index 807355d59..1a14c9777 100644 --- a/internal/tui3/home.go +++ b/internal/tui3/home.go @@ -3654,7 +3654,14 @@ func (a *app) homeStartWithProject(text, place string) tea.Cmd { // ruling). It is spent on the way OUT rather than on the way in, so a door that // refused leaves the pin a person set exactly where they set it. func (a *app) homeOpenAtTarget() (tea.Cmd, bool) { - where := strings.TrimSpace(a.targetWhere()) + return a.homeOpenAt(a.targetWhere()) +} + +// homeOpenAt opens the target captured while the draft is still visible. +// Consuming a slash draft rebuilds Home's rows and can move its cursor to a +// different project; that new selection must not redirect the submitted work. +func (a *app) homeOpenAt(target string) (tea.Cmd, bool) { + where := strings.TrimSpace(target) if where != "" && where != strings.TrimSpace(a.workspace) { // THE TRAY GOES WITH THE PERSON, and carrying it means taking it OUT of // the conversation being stepped aside from before the aside is stowed — diff --git a/internal/tui3/home_task_project_regression_test.go b/internal/tui3/home_task_project_regression_test.go new file mode 100644 index 000000000..bca9569a7 --- /dev/null +++ b/internal/tui3/home_task_project_regression_test.go @@ -0,0 +1,39 @@ +package tui3 + +import ( + "errors" + "strings" + "testing" + "time" +) + +func TestHomeTaskKeepsTheProjectShownBeforeSubmitting(t *testing.T) { + for _, text := range []string{"write a shopping report", "/task write a shopping report"} { + t.Run(strings.ReplaceAll(text, " ", "_"), func(t *testing.T) { + lab := newHomeLab(t) + projectA, projectB := lab.workspace("alpha"), lab.workspace("beta") + lab.session("-tmp-alpha", "aaaa000000000001", "other active project", projectA, time.Now()) + mine := lab.session("-tmp-beta", "bbbb000000000002", "current project", projectB, time.Now().Add(-time.Hour)) + a := lab.app(mine) + t.Cleanup(a.leaveEverything) + runCmd(a.openHome()) + typeHome(a, text) + before := a.targetWhere() + if before != projectB { + t.Fatalf("fixture target before submit=%q, want %q", before, projectB) + } + var asked string + a.start = func(workspace string) (Conversation, error) { + if workspace == "" { + workspace = projectB + } + asked = workspace + return Conversation{}, errors.New("captured target before opening") + } + runCmd(a.homeEnter()) + if asked != before { + t.Fatalf("submission opened %q after showing %q", asked, before) + } + }) + } +} diff --git a/internal/tui3/homeslash.go b/internal/tui3/homeslash.go index 3da5b926b..5e1a95954 100644 --- a/internal/tui3/homeslash.go +++ b/internal/tui3/homeslash.go @@ -296,6 +296,9 @@ func (a *app) homeSlash(line string) tea.Cmd { name, rest, _ := strings.Cut(strings.TrimPrefix(line, "/"), " ") rest = strings.TrimSpace(rest) word := canonicalCommand(strings.ToLower(name)) + // Bind the submission before clearing the draft rebuilds the list. The + // folder shown beside this draft is its destination, not the next row. + target := a.targetWhere() h.box.reset() h.build() switch homeFate(word, rest) { @@ -334,7 +337,7 @@ func (a *app) homeSlash(line string) tea.Cmd { // used to compact a conversation behind the screen, and `/files` opened a // shelf over one. Both are now about the conversation this line is // opening, which is the conversation the rule above the box named. - started, opened := a.homeOpenAtTarget() + started, opened := a.homeOpenAt(target) if !opened { return nil }