feat(sidebar): grouped category render + UI (LC-79 phase 1b) #130

Merged
nrupard merged 1 commit from feat/lc-79-sidebar-categories-phase1b into main 2026-05-18 17:15:14 +02:00
Owner

Summary

LC-79 phase 1b: surfaces the categories persisted by phase 1a PR #129 in the sidebar render. Together they cover LC-79's phase 1 scope (schema + CRUD + grouped render). Phases 2 (drag-and-drop) and 3 (collapsed-aggregate unread, mobile polish) are separate follow-ups.

Closes LC-79 phase 1b.

Data flow

load_sidebar returns a third value alongside rooms and peers: a Vec<SidebarCategoryGroup>. Each group bundles its rooms in user-controlled position order. Rooms with no assignment stay in the existing sidebar_rooms vec, rendered under "All rooms" when at least one category exists, or under the original "Rooms" header when none do. The bucketing is two small queries against auth.db (categories + assignments), no chat.db work added.

The new field threads through every view struct that today carries sidebar_rooms: &[SidebarRoom] (~25 sites across views/{enclave,home,bookmarks,admin,pinned,settings,voice,dm,room,not_found,ws_fragments}.rs) and every handler that constructs them (~30 sites in routes/). Mechanical pass: a regex-driven batch update covered the call sites uniformly.

Sidebar UI

partials/sidebar.html renders, in order:

  1. Each category in position order. Header is a PATCH /sidebar/categories/{id} form that toggles collapsed (sends collapsed=1 when currently expanded so submit collapses; omits the field when collapsed so submit uncollapses). When expanded, the room list appears below.
  2. A menu on each category surfacing rename (PATCH /sidebar/categories/{id} with name=…) and delete (DELETE /sidebar/categories/{id} with a confirm prompt that mentions room fallback).
  3. The "All rooms" / "Rooms" fallback for uncategorized rooms.
  4. The "Direct messages" section (unchanged).
  5. A small + Add form (POST /sidebar/categories) at the bottom of the nav scroll area.

partials/sidebar_room_row.html (new) renders one room row. When sidebar_categories is non-empty, a hover-revealed menu lists every category as a PATCH /sidebar/categories/{id}/rooms/{room_id} action plus a "Remove from category" entry (DELETE /sidebar/categories/rooms/{room_id}). The menu hides itself entirely when no categories exist so a user who has never used the feature sees the same sidebar as before.

Compat

  • Users with zero categories see no visible change beyond the new + Add form at the bottom of the sidebar.
  • All existing routes that load load_chrome / load_sidebar now also pass sidebar_categories through; no functional drift expected since the new field defaults to an empty slice for those users.

Test plan

  • just check (fmt + clippy across standalone + saas).
  • just test (full standalone integration suite, all green).
  • ./dev/cargo test -p lets-chat-server --test routes_sidebar_categories (6 tests from phase 1a still pass).
  • Manual smoke: create a category, assign a room to it, verify the room moves to the category section; collapse it; refresh the page (state persists); rename it; delete it (room falls back to "All rooms").

Follow-ups

  • Phase 2: drag-and-drop reorder (HTML5 DnD + PATCH /sidebar/categories/{id}/positions).
  • Phase 3: aggregate unread / mention badge on collapsed categories; mobile responsive polish.
## Summary LC-79 phase 1b: surfaces the categories persisted by [phase 1a PR #129](https://dev.a8n.run/a8n-tools/lets-chat/pulls/129) in the sidebar render. Together they cover LC-79's phase 1 scope (schema + CRUD + grouped render). Phases 2 (drag-and-drop) and 3 (collapsed-aggregate unread, mobile polish) are separate follow-ups. Closes [LC-79](https://niceguyit.myjetbrains.com/youtrack/issue/LC-79) phase 1b. ## Data flow `load_sidebar` returns a third value alongside rooms and peers: a `Vec<SidebarCategoryGroup>`. Each group bundles its rooms in user-controlled position order. Rooms with no assignment stay in the existing `sidebar_rooms` vec, rendered under "All rooms" when at least one category exists, or under the original "Rooms" header when none do. The bucketing is two small queries against `auth.db` (categories + assignments), no chat.db work added. The new field threads through every view struct that today carries `sidebar_rooms: &[SidebarRoom]` (~25 sites across `views/{enclave,home,bookmarks,admin,pinned,settings,voice,dm,room,not_found,ws_fragments}.rs`) and every handler that constructs them (~30 sites in `routes/`). Mechanical pass: a regex-driven batch update covered the call sites uniformly. ## Sidebar UI `partials/sidebar.html` renders, in order: 1. Each category in position order. Header is a `PATCH /sidebar/categories/{id}` form that toggles collapsed (sends `collapsed=1` when currently expanded so submit collapses; omits the field when collapsed so submit uncollapses). When expanded, the room list appears below. 2. A `…` menu on each category surfacing rename (`PATCH /sidebar/categories/{id}` with `name=…`) and delete (`DELETE /sidebar/categories/{id}` with a confirm prompt that mentions room fallback). 3. The "All rooms" / "Rooms" fallback for uncategorized rooms. 4. The "Direct messages" section (unchanged). 5. A small `+ Add` form (`POST /sidebar/categories`) at the bottom of the nav scroll area. `partials/sidebar_room_row.html` (new) renders one room row. When `sidebar_categories` is non-empty, a hover-revealed `≡` menu lists every category as a `PATCH /sidebar/categories/{id}/rooms/{room_id}` action plus a "Remove from category" entry (`DELETE /sidebar/categories/rooms/{room_id}`). The menu hides itself entirely when no categories exist so a user who has never used the feature sees the same sidebar as before. ## Compat - Users with zero categories see no visible change beyond the new `+ Add` form at the bottom of the sidebar. - All existing routes that load `load_chrome` / `load_sidebar` now also pass `sidebar_categories` through; no functional drift expected since the new field defaults to an empty slice for those users. ## Test plan - [x] `just check` (fmt + clippy across standalone + saas). - [x] `just test` (full standalone integration suite, all green). - [x] `./dev/cargo test -p lets-chat-server --test routes_sidebar_categories` (6 tests from phase 1a still pass). - [ ] Manual smoke: create a category, assign a room to it, verify the room moves to the category section; collapse it; refresh the page (state persists); rename it; delete it (room falls back to "All rooms"). ## Follow-ups - Phase 2: drag-and-drop reorder (HTML5 DnD + `PATCH /sidebar/categories/{id}/positions`). - Phase 3: aggregate unread / mention badge on collapsed categories; mobile responsive polish.
feat(sidebar): grouped category render + UI affordances (LC-79 phase 1b)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 55s
53e601226d
Surfaces the categories persisted by phase 1a in the sidebar. `load_sidebar` now buckets categorized rooms into `SidebarCategoryGroup`s alongside the uncategorized fallback; the new field threads through every view struct that renders the sidebar (~25 sites). `partials/sidebar.html` iterates categories first (in user-controlled position order, collapsed-aware) then the "All rooms" fallback then DMs. A small "+ Add" form at the bottom of the nav lets the user create a category; each category gets a `…` menu with rename + delete; each room gets a hover-revealed `≡` menu listing all categories plus "Remove from category".

Collapsed-state toggle: the category header is a `PATCH /sidebar/categories/{id}` form that includes `collapsed=1` when currently expanded (so submit collapses) and omits it when collapsed (so submit uncollapses), matching the existing checkbox semantics in `PatchCategoryForm`.

Per-room move dropdown only renders when `sidebar_categories` is non-empty; users with no categories see the same sidebar shape as before phase 1a, just with the "+ Add" affordance below the list.

Phase 2 (separate PR): drag-and-drop reorder. Phase 3: unread aggregation on collapsed categories + mobile layout polish.
nrupard deleted branch feat/lc-79-sidebar-categories-phase1b 2026-05-18 17:15:14 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!130
No description provided.