Skip to content

feat(tabs): place new, child and web tabs into groups - #528

Draft
aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:pr/grouped-rail-placement
Draft

aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:pr/grouped-rail-placement

Conversation

@aakhter

@aakhter aakhter commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Based on master (the grouped-rail stack #517, #519, #525 and #526 shipped in 1.34.0 and 1.35.0). One commit.

This lets new, spawned and saved-web tabs land in tab groups.

New session in a group. A group's menu gains "New session": it expands the group and launches one session with the current case and run mode.

  • The group id rides the create request (tabGroupId on POST /api/sessions and /api/quick-start).
  • TabLayoutService.sessionCreated() places the session at the end of that group in the same versioned write that first records it. The session joins the live map inside the owner lock, so nothing queued can commit it first, and a failed write rolls the registration back.
  • An unknown or foreign group id is ignored rather than failing the spawn, and a malformed one is a 400.
  • The response returns the committed layout, so the tab is drawn in its group immediately. Requests without the field are unchanged.

Children follow their parent. A session spawned by another one follows its parent into the parent's group until it is moved by hand.

  • The row menu shows where a child sits, and offers "Follow parent again" for a hand-placed child.
  • A child whose parent is gone stands on its own and is never re-adopted.
  • While groups exist, the browser adopts the server's order on create and delete instead of echoing its own guess back through PUT /api/session-order. That echo used to pin every child in place.

Web tabs. Saved web tabs keep per-device openness: a grouped web tab shows only while it is open on this device, and reopens where the layout keeps it.

Unchanged: the flat rail, the header strip, the tree semantics, sessionOrder and Alt+N. No TabLayout model changes; normalizeTabLayout already handled child follow.

Tests cover the service (atomicity, owner scoping, rollback, follow vs manual), the routes (via app.inject), the launch paths, the rail (jsdom), and end to end through a real WebServer. docs/api-reference.md documents tabGroupId.

For review

  • If session:created arrives before the POST response, a grouped session can show in Ungrouped for one render until the layout read lands. I left the layout out of the SSE payload to keep the SSE contract unchanged.
  • In multi-user mode, an admin still sees other users' saved web tabs (from GET /api/webviews). They render in Ungrouped with no move actions, since they are not in the admin's layout.

@aakhter

aakhter commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Draft only because GitHub won't let my account open regular PRs or mark drafts ready right now. It's ready for review. Fifth in the grouped-rail stack (#517 → #519 → #525 → #526 → this), only the last commit is new.

@aakhter
aakhter force-pushed the pr/grouped-rail-placement branch from e16af49 to 42ee7aa Compare October 5, 2026 00:31
@aakhter

aakhter commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the updated #526, which is on the updated #525 (on master 1.34.0). The new "Follows …" and "Placed by hand (parent: …)" menu labels name a parent session, not a group, so they stay unquoted, while every "Move to" label now uses #525's quoted form. Still only the last commit is new.

New sessions, spawned child sessions and saved web tabs now land in tab
groups instead of always starting in Ungrouped.

New session in a group:
- The group menu gains "New session" (first item). It expands a collapsed
  group and runs ONE launch with the current case and run mode; a launch
  already in flight is reported, not queued.
- run({ count, tabGroupId }) threads the group through every launch path
  (local POST /api/sessions, remote/docker and external-CLI
  /api/quick-start). The count overrides the steppers for that launch only.
- Server: POST /api/sessions and /api/quick-start accept an optional
  tabGroupId (Zod, 1-100 chars; null/empty is a 400). The routes pass it to
  addSession(), and TabLayoutService.sessionCreated() places the session at
  the end of that group in the SAME versioned write that first records it.
  The session is added to the live map inside the owner lock (with a
  rollback when the write fails), so no queued layout edit can commit it
  first. The group is looked up in the creating owner's own layout, so an
  unknown, deleted or foreign id is ignored instead of failing the spawn. An
  explicit group is a hand placement: a child placed this way is manual.
- The create response carries the committed tabLayout (only when tabGroupId
  was sent), adopted before the new tab is drawn. Responses without the
  field are unchanged.

Child sessions (parentSessionId / X-Codeman-Parent-Session):
- A child nobody placed follows its parent: the server's existing
  normalization already puts it after the parent's rows; the browser now
  draws a not-yet-stored child there too (project({ parents })) and, while
  groups exist, adopts the server's order instead of echoing its own append
  back through PUT /api/session-order (which pinned every child manual).
- The row menu says where a child sits (follows, placed by hand, parent
  closed, parent loop) and offers "Follow parent again" only to a hand-placed
  child. A child whose parent is gone stands on its own and is never
  re-adopted.

Saved web tabs:
- Web tabs are layout refs like sessions; openness stays per device. A
  grouped web tab shows (and takes an Alt+N slot) only while open here and
  reopens where the layout keeps it. Pinned by tests; no behaviour change.

Unchanged: the flat rail and header strip (markup, HTML5 drag, append and
save on create), the tree semantics and single tab stop, sessionOrder and
Alt+N, and every request that does not send tabGroupId.
@aakhter

aakhter commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto master (1.35.0), so this is now one commit on top of the shipped rail, keeping your 737a252 and 566365e behaviour as is. The only textual conflict was the Owner tab layouts paragraph in CLAUDE.md: I kept yours, including the pagehide keepalive exception, and added this PR's sentences after it. "New session" in the group menu adds a tab rather than a group, so it stays offered at the 32-group cap, while "New group" stays behind _canCreateTabGroup(). Your new cap test now expects "New session" first in that menu. The tab-layout, tab-group, route and rename tests and the browser suites all pass. It's still a draft only because GitHub won't let my account mark PRs ready; it's ready for review.

@aakhter
aakhter force-pushed the pr/grouped-rail-placement branch from 287321e to be4f849 Compare October 6, 2026 01:24
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.

1 participant