Skip to content

tui3: conversation overview - #1429

Merged
AbirAbbas merged 90 commits into
devfrom
feat/conversation-overview
Sep 25, 2026
Merged

AbirAbbas merged 90 commits into
devfrom
feat/conversation-overview

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Adds a way to see every open conversation at once, group them into teams, and hand a team to a manager conversation.

What it adds

  • The conversations wall (alt+v, or ▦ All on the tab strip). Every open conversation as a live card: what it is doing, what it spent, whether it wants you. Cards open, close and filter by team without moving focus.
  • Teams. A conversation can belong to several teams. Each team has a name and a colour, shown as a chip on the tab strip that narrows the tabs to that team. Teams live in one store, internal/teams (teams.json with a file lock and stamps, plus a Traffic log per team).
  • Organize. One button asks a model for a proposed grouping. Nothing changes until you apply it, and an apply can be undone.
  • The manager. Any conversation can be made a team's manager. What you type in it goes to the manager, which uses team tools (team_status, team_read, team_send, team_stop, team_start) to hand work to members. Members' replies wake it. The manager's screen shows Traffic, the team's messages as threads, next to the conversation.
  • @ references to a team or a conversation in the composer. Team names in a chat are clickable.
  • --host. Teams and Traffic work over --host through new wire methods (internal/remote/wire_teams.go).
  • Docs. Manual pages conversations-and-teams.md and team-manager.md, and a design doc, docs/design/conversations-and-teams/DESIGN.md.

How to try it

  1. Open a few conversations, then press alt+v (or click ▦ All) to see the wall.
  2. On the wall, make a team from a card or press Organize and apply its proposal.
  3. Open the team switcher (the ● harbor ▾ chip on the tab strip) and choose ◆ Make this harbor's manager on one of its conversations. Ask it to get a status from the team, and watch Traffic fill on the right.

Where to start reviewing (191 files, about 35k lines, most of it tests)

  • Start with the design doc. docs/design/conversations-and-teams/DESIGN.md, sections 1 to 5, states each rule the code follows and what was ruled out.
  • The store. internal/teams, beginning with store.go, teams.go and traffic.go. This is small, self-contained and carries the concurrency.
  • The manager's behaviour. internal/session/team.go, tools_team.go and team_wake*.go: the role note, the tools and the wake rules (coalesced, capped per hour, with a loop breaker).
  • The interface. internal/tui3: wall*.go, team*.go, mention.go, place_chats.go. About half the diff by volume, and most of it is tests pinning layout, hover and focus rules.
  • --host. internal/remote/teams.go and wire_teams.go, then cmd/codeaf/chatv3_host_teams.go.

Evidence

  • Latest dev merged in (a merge commit, not a rebase). The head is 6416a6cf9, 0 commits behind dev.
  • GitHub CI is green: light gate, touched packages, check.
  • Spark, a fresh clone at 6416a6cf9 with OPENROUTER_API_KEY unset: ./cmd/codeaf, ./internal/manual, ./internal/remote, ./internal/session, ./internal/teams, ./internal/tui3 all pass. gofmt is empty and the change-notes check is clean.

#1494 (the team delegation work: a teams page, questions going up the team tree, caps, closing teams) is stacked on this one and follows it.

🤖 Generated with Claude Code

Tile bodies hang from the top under one blank row; a waiting tile ends in
its question and an Answer button. The Spaces row is one evenly padded
segmented control ending in + New space. The foot mirrors the head, the
grid is centred between the two rules, and the head and toolbar end where
the grid ends. The toolbar says what the hovered control does. Cards and
popovers share one padding, keep their primary action last, and stay
inside the frame and off the foot. A filter or space with nothing in it
keeps the head and offers the way back. The tile look's border field is
renamed so it no longer reads as the live-state edge.
…strip has a door to the wall

A tile's top border now carries only its space dots and title. Under the
pointer or the keyboard's focus its bottom border becomes a labelled action
row (Open or Answer, Select, Spaces, Close, each with its key), in cells
fixed by the tile's width, dropping buttons from the right on narrow tiles.
The selection box is drawn on the top border only in the selection mode,
on every tile.

? opens a help sheet listing every act the page has in four groups; every
row is a press that does what its key does. The next-needing-you key moves
to n. The toolbar gains a Help button, dropped last on narrow frames.

The tab strip draws a door to the wall after the new-chat +, lit while the
wall is up, which opens and closes it and names alt+v in the hint slot.
Every word the wall, the strip chip, the dock and the help sheet draw now says team: the Teams row, + New team, the New team card, the Teams popover, Add to teams and Team settings. The files, types, helpers, hit kinds and tests are renamed to match, and /wall and its manual line mention teams.
…eams.json

teams.json is version 2: every team has a random id, a parent, a reserved manager and its colour, and fields a later build writes are kept through a load and a save. The active team, each team's place on the wall, the popovers and every pointer target name a team by id, so deleting or reordering one never retargets another. teamSetParent refuses loops and missing parents, and a deleted team hands its children to its parent. The first build's spaces.json is read once, written as teams.json with the colours it was drawn with, and renamed to spaces.json.migrated.
The new-team card still offers the shared folder's name, and otherwise a pleasant word at once, while one call on the naming role asks the agent for a one to three word name over the chosen conversations' titles. It runs off the update loop, says naming beside the field, and replaces the word only if the person has not typed; a failure or five seconds without an answer keeps the word. A team is never renamed afterwards except in its settings.
/new, which the strip's + and the start page also take, and a folder typed on home put the conversation they mint into the active team and save it, at the moment it first has its key. Switching to a conversation that already exists changes no team. A member closed before it had a name is not drawn on the strip, and saving refreshes each member's name from its tab.
A press on the chip opens a menu under it on every page, the conversations view included: each team and All with its open count, then Add or Remove this conversation for the shown team, New team, which opens the card with the conversation in front picked, and Team settings. The arrows, enter and esc drive it, and a press off it only puts it away. With teams but none shown the chip is a quiet Teams, so the first team can be chosen from the strip, and teams are read once at start so it is there from the first frame.
The new chat page covers opening the conversations view, reading a tile, the action row, selection, teams (making and naming one, colours, membership, showing one, auto-join, settings and delete), the strip's team switcher, where teams are kept, and every key. keys.md names alt+v and the view's keys, and the tasks page's handover heading answers the question about handing work to a team of workers, which the new page would otherwise take.
TestWallRenderPrintsFrame prints the new-team card while a name is asked for, and TestTeamMenuPrintsFrame prints a 120-column chat with the switcher open. The last test still named after spaces is renamed.
One call on the naming role, the same door and billing as NameTeam, with one fall-through rung at most. The model is given conversation titles and folders and the existing teams by short ref, and answers JSON of new teams and additions. The answer is validated strictly: unknown refs and repeats are dropped, names are cleaned as team names, a new team of fewer than two is dropped, and a new team named like an existing one becomes additions to it, so nothing is ever renamed or removed.
The Teams row ends in an Organize button on All (o), counting the conversations in no team from five, and reading Organized once a run found nothing and nothing changed. It opens a card of suggestions, each ticked: a folder pass that is free and exact, merged with one ProposeTeams call off the update loop with a ten second bound, the folder pass winning. Without a model answer the folder pass is shown alone and says so. Apply makes the ticked teams in one save, and the Teams row offers Undo (u) for six seconds, which restores the exact prior list. The help sheet and the key map list o and u.
Where the button is, where suggestions come from, the folders-only and nothing-to-suggest states, ticking and applying, Undo, and the o and u keys.
internal/teams holds the Team and Member types, teams.json v2 with migration from spaces.json, repairs, unknown-field round trip and atomic save, all through config.ProfilePath. Writes go through Update, a read-modify-write under an exclusive filelock with a bounded wait. Members get stable per-team handles, a team has at most one manager who is always a member, each team has an append-only Traffic log with rotation, and Digest renders a team for its manager's context.
The store moved out: team and teamMember are aliases of teams.Team and teams.Member, load, save, the tree walks, the set-aside of an unreadable file and the hue arithmetic call the package. The interface keeps the palette's reserved hues, drawing, naming and the strip. No change in what the wall or the strip does; the frame law now also names the store's disk doors.
Each edit the interface makes to the teams goes through teams.Update
(app.teamEdit): the change is made to what the window holds, then again
to the file as it is on disk under the lock, and the window adopts what
was written. A manager, handle or member another process wrote since the
window loaded is kept. Members take their tab's title on the way, so an
untitled member gets a handle once it has a title. Organize's Undo takes
back exactly what Apply did instead of saving the old list.
A team shown puts its manager first: the manager's own tab named for the
team (a diamond and the team name), or a quiet + Manager word button while
it has none, which starts a conversation in the team's folder and makes it
the manager (AddMember then SetManager). The team switcher and a tile's
Teams popover offer Make manager on a member and Remove manager on the
manager. The manager's tile is pinned first on the wall and marked.
… start

While this window holds a conversation in a team, a clock of its own reads
each such team's Traffic log off the loop (beside the door line), paging
forward from a cursor, and folds it into a cache; a first look reads the
tail, so a window opening never acts on what came before it. The same read
adopts a teams file another process changed and titles members that joined
untitled.

With the manager in front, the frame gives the right of the body to the
team's traffic, newest at the bottom, each row a door to its member; on a
narrow frame the column is a two-cell edge that lays the traffic over the
body. The composer's placeholder says to the manager.

KindStop ends the current turn of a conversation this window holds, as the
person's stop does; KindStart, in the window holding the manager, opens a
conversation in the team's folder under the manager's handle and sends the
brief marked as the manager's. Each is done once per entry id.
What a manager is, who outranks whom, the five manager verbs and the
member's post with their approval defaults, and when team messages arrive.

Claude-Session: https://claude.ai/code/session_0197RHyWZLcLUSyoJusXMoaF
A conversation finds its teams by its own conversation key (the transcript
path, cleaned and symlink-resolved, the key tui3 stores a member under) and
is a manager where Team.Manager is that key.

A manager is armed with team_status, team_read, team_send, team_stop and
team_start; a member of a managed team with team_post. They arrive through
armFamily at the first step boundary that finds the team, never in belt(),
so the fixed prefix is unchanged. Every write is one AppendTraffic entry per
the Traffic contract (note, directive, stop, start). team_start asks under
the shipped default; the rest sit on the builtin approval floor.

At every step boundary, Traffic addressed to this conversation since its
cursor is delivered as one marked session note ("◆ from manager: ...",
"from @web: ..."), once: the cursor lives in the session folder's team.json,
and a fresh conversation starts at its birth or its own start, so history is
not replayed. A manager's turn carries teams.Digest with its brief in a note
of its own, read beside the work at turn start; member states come from each
member's journal tail.

Claude-Session: https://claude.ai/code/session_0197RHyWZLcLUSyoJusXMoaF
Both engine wake tests waited for "◆ directive from manager: Fix the
header." and timed out at 20s once delivered lines carry their entry
number ("◆ directive from manager #1: ..."). The wake itself was
never broken: with the number dropped the old assertion passes on the
same tree. They now wait for a line marked as the manager's directive
whose words are the directive's, and say what the model was sent when
it never arrives.
santoshkumarradha pushed a commit that referenced this pull request Sep 25, 2026
…branch

One merge rather than a rebase: 26 files conflict in the net, and replaying
the 30 delegation commits would resolve about 140 file conflicts, the same
place-order, DESIGN numbering and Traffic hunks again and again, with every
intermediate commit untestable.

The wake commits both sides carry are kept once, in delegation's form where
wake is an inheritable team setting; #1429's later wake work (team_start
honours wake off, the answers a wake carries) is applied on top of it.

Place order is home teams chats sessions spend settings, then standing,
memory, search: teams alt+2, chats alt+3, and so on to search alt+9. The
threaded Traffic rail draws rulings, sub-team starts, questions and their
answers, packets and closings. team_send keeps #1429's several-handles entry
and delegation's link and sub-team refusals; a directive to everyone with
shared members is one entry to several.

The teams page's draws ask which doors the seam has (teamsCanDelegate,
teamsCanWrapUp, teamsCanReadHistory) rather than binding the local seam, whose
closures read the disk, so the frame law holds with both sides' paint paths.
…ss goes.

A row of squares did not explain itself. The dim word opens the conversations view, and each square names its conversation and state.
santoshkumarradha pushed a commit that referenced this pull request Sep 25, 2026
…tself

# Conflicts:
#	docs/design/conversations-and-teams/DESIGN.md
#	internal/tui3/walldock.go
Brings in #1410, #1440, #1426, #1389, #1388, #1422, #1334, #1424, #1425
and #1415. Conflicts resolved keeping both sides: the keys row carries
dev's project and low-credit line at its right end and the dock in what
they leave; the strip's home chip takes dev's word on the one top bar's
lead; the chats place joins dev's restored esc and double-space manual.
The icon law (internal/iconlaw) refused ten literals the conversations
wall and the teams added: the menu's empty radio, the organize check,
the traffic's finished and failed marks, the zoom's closing corners,
the dock's square and the tile cell. Each is now the vocabulary's slot
through palette.glyph, app.icon, the frame's pieces, or the table's own
spelling where the mark is a shape and not a state.
Dev's #1424 pinned the strip's home chip at tabLead, where dev's places
bar stands; on this branch the places bar moved to the strip's lead
(placeBarLead, DESIGN.md's one top bar), and the chip stays with it. The
tests now ask for placeBarLead and the lowercase word, which is the
invariant #1424 was after: the same cells in both views.
@santoshkumarradha
santoshkumarradha marked this pull request as ready for review September 25, 2026 14:07
@santoshkumarradha

Copy link
Copy Markdown
Member Author

@AbirAbbas this is ready for review and merge. It is up to date with dev and CI is green. #1494 (team delegation) is stacked on it and comes next.

santoshkumarradha pushed a commit that referenced this pull request Sep 25, 2026
…ation

Brings dev in through #1429. This branch's header stands: the places on
the top line, the chat strip only inside a chat, no home piece on the
strip. Dev's rules folded in: space space opens home and esc returns to
the chat (manual follows, with chats at alt+3 here), the rail's run
rows, the attribution-model setting, the plan digest on a person's turn.
The side column's Traffic marks and the teams page's marks ask the glyph
door, as the icon law requires.
AbirAbbas and others added 4 commits September 25, 2026 11:20
Every ordinary launch reads teams through the engine's Teams.Read door, so
the window never reached the local seam that moves a bad file aside. A
truncated teams.json then refused every team edit for good ("teams.json is
unreadable") while the manual promised it is moved aside. The engine's read
now renames it to teams.json.unreadable-<nanos> and answers empty, as the
local seam does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only a window that has the member open performs a stop, and a member codeaf
opened in the background to run a woken turn has none. The tool told the
manager such a member had no turn running, and the manual said the stop
always ends the turn. Both now say what happens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`/wall` opens it and people call it the wall, but "what is the
conversations wall" reached the -timeout wall pages. The heading carries the
word now, and two probes pin it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	internal/tui3/app.go
#	internal/tui3/footswap.go
@AbirAbbas

Copy link
Copy Markdown
Collaborator

Reviewed this and checked it by hand in the real binary (deepseek-v4-flash on every seat, 52 calls, under a cent). These all hold:

  • the wall via alt+v, ▦ All and /wall;
  • a team made from a card, with the ● harbor ▾ chip;
  • Organize: propose, then apply (Organized · 1 new team Undo), then an undo that restores teams.json byte for byte;
  • ◆ Make this harbor's manager, with the manager using team_status, team_read and team_send while Traffic fills;
  • a member's reply waking the manager;
  • @ references and team-name clicks.

No blockers. #1487 landed on dev after your check and conflicts with this branch in internal/tui3/app.go (#1487's defer a.stirred() kept ahead of the team-switcher and wall press handling) and internal/tui3/footswap.go (#1487's tip placed in the project's slot inside your dock layout). I'm pushing a plain merge of dev plus three small fixes on top of 6416a6cf9, with no rebase:

  • 6d297d060 remote: the engine's teams read sets an unreadable teams.json aside. Every ordinary launch reads through the engine's Teams.Read door, which never reached the local seam's set-aside. A truncated teams.json therefore refused every team edit for good (teams.json is unreadable), although the manual says it is moved aside. There's a test that failed first, and it's re-checked by hand.
  • f26f25c04 session: team_stop says a background-opened member is not stopped. The tool's reply and the manual claimed it could stop one. Making the engine actually stop it is left to you.
  • 211f1d644 manual: the conversations view answers to the wall. "What is the conversations wall" reached the -timeout wall pages. There are two probe tests for it.

Should-fixes, not blocking:

  • The body says tiles show "what it spent", but no tile draws spend.
  • A member reply can wake the manager twice: its post, then its finished event about 13 s later, past the 5 s settle. That roughly doubles cost per round, and the 10-round breaker trips after about 5 real rounds. The bound itself holds: a scratch test stopped at 22 manager and 22 member calls.
  • A refused write still draws its toast.
  • A zero or negative Budget gets no deadline.
  • A demoted manager keeps its team tools.
  • Wall tiles show the wake-prompt text that the conversation view hides.

Merging once CI is green on the new head. #1494 then retargets to dev, and I'll do its dev merge as said in Slack.

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified by hand in the real binary and on top of dev at 7f136b3 (details in my comment above); CI green on 1abf425. Merging.

@AbirAbbas
AbirAbbas merged commit 5d8324a into dev Sep 25, 2026
4 checks passed
@AbirAbbas
AbirAbbas deleted the feat/conversation-overview branch September 25, 2026 16:01
AbirAbbas added a commit that referenced this pull request Sep 25, 2026
…and #1495

Four tui3 conflicts, each #1429's wall and team code beside the plan page
this branch removes. Both are kept: the wall's paint term stands before the
task room's, the wall, team menu and zoom keys are read where the plan page's
and work tab's keys were, the row keeps both its plan and open fields, and the
frame draws the wall where it drew the plan page and the work tab.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AbirAbbas pushed a commit that referenced this pull request Sep 25, 2026
…1485)

Every task, on either engine, opens the task room: transcript and work tabs, a note box that reaches the store, x through the stop card, the model and tokens in the head, and child rows that open their own room. The separate plan page, its p key and its direct part cancel are gone.

Added in review: a finished or stopped run's work tab reads the work off its branch instead of saying the copy is gone, the work tab draws file bytes through drawableLine, and the manual no longer describes the plan page; dev merged in over #1429.

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ZeroPoint95 added a commit that referenced this pull request Sep 25, 2026
Brings in #1429 (conversation overview) and #1485 (one task page for
every task). #1485 retired the page senior-dev's stored page was drawn
on, so a program's task now opens its program room from every door —
its row, its card, the tasks place and the run's tab. A program room
draws no transcript/work tabs, the run's own tab stays the one selected
tab, and the branch's guards for the retired overlays are dropped or
pointed at the room. Tests that drove the old page drive the room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Sep 25, 2026
#1429 landed on dev as one squashed commit, so dev no longer shares the
commits this branch merged from it (up to 6416a6c). This commit has
exactly the squash's tree and both as parents, so merging it tells git the
two are the same work and only the real conflicts with dev remain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Sep 25, 2026
Merged through a commit that stands for #1429's squash with the branch this
one was built on (6416a6c) as its second parent, so only the real
conflicts show.

tabtitle: #1496 put the hovered tab's full title on the head's blank row under
the rule; on this branch a chat's head is the nav, the strip, the rule and the
blank, so that row is chatHeadRows-1 and the strip's row is tabStripRow.

notice: #1487's quiet tip reads the keys row of a put-away column, which this
branch spells through sideBackHint (`alt+l tasks`, `alt+l traffic` for a
manager) rather than the retired railBackHint.

manual: #1495's workers heading stands before this branch's rewritten roster
paragraph; team_stop keeps #1429's background-member sentence beside this
branch's new rows; the dock and wall probes are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AbirAbbas pushed a commit that referenced this pull request Sep 25, 2026
Delegation around team managers: questions go up the team tree as decision packets, settings inherited with their provenance shown, nested teams and an optional All teams manager, wrap-up and close with a deadline that survives a restart, and spending caps that raise one packet. A teams place, the places on the wordmark line, one side column with Tasks and Traffic, and a standing card that says what it is.

Added in review: dev merged in over #1429's squash (through a stand-in merge so the squash does not conflict with this branch's own edits) and #1485 (the task room keeps this PR's one-line child rows; the plan page stays gone).

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants