feat: per-case default session group

Case settings gains a "Default session group" selector (Ungrouped plus every
named tab group of the owner). A new session of the case lands in that group
in the same versioned layout write that registers it, so it is drawn in place
with no Ungrouped-to-group jump.

- Stored per owner and case in state.json (caseDefaultGroups), as the group's
  stable id or null for Ungrouped; read and written only through
  TabLayoutService under the owner lock.
- Precedence in sessionCreated: explicit tabGroupId, then the parent
  placement, then the case default, else Ungrouped. A deleted or foreign id
  never fails a spawn and is forgotten only after the creating write lands.
- POST /api/quick-start (local, linked, remote, Docker) and
  /api/ralph-loop/start pass their case; POST /api/sessions infers it
  lexically from workingDir. The create response carries tabLayout when a
  case default placed the session.
- GET/PUT /api/cases/:name/default-group; deleting a case (or an admin
  deleting a user's case folder) forgets the value.
- The selector mirrors desktop and phone, reverts on a refused save, and is
  redrawn from every adopted tab layout; zh-CN strings added.
This commit is contained in:
Aamer Akhter committed 2026-10-22 03:42:42 -04:00
1 parent 39077281d1
commit 74c980413a
24 files changed
+1362 -26

No files matched your search

+6
View File
@@ -121,6 +121,12 @@ export interface AppState {
sessionOrder?: string[];
/** Owner-scoped authoritative grouped tab layouts. */
tabLayouts?: Record<string, TabLayout>;
/**
* Per-case default session group, keyed by the tab-layout owner key, then case
* name. A group id from that owner's layout, or null for an intentional
* Ungrouped choice. Read and written only through TabLayoutService.
*/
caseDefaultGroups?: Record<string, Record<string, string | null>>;
}
// ========== Default Configuration ==========