Home page: group boards by workspace when "All Workspaces" is selected #86

Open
opened 2026-04-28 09:52:37 +00:00 by AhmedHanafy725 · 3 comments
Member

Improvement

When the workspace filter dropdown is on All Workspaces, the home page renders every board in a single flat grid sorted by recency. There is no visual cue showing which workspace each board belongs to. As soon as a user has more than a couple of workspaces, scanning the grid becomes confusing — two boards with similar names from different workspaces look identical, and there's no way to mentally group what you're seeing.

When a specific workspace is selected the grid is fine; the issue only appears for the "all" view.

Best practice

Products in the same space (Miro, Trello, Linear, Notion's database views, GitHub project boards) all solve this the same way: when listing entities across multiple parents, group by parent with section headers. Specifically:

  • A small section header per workspace (workspace name + live board count).
  • Boards rendered in a sub-grid under each header.
  • Sections ordered the same way the workspace dropdown is ordered (most recently updated first), so the user's mental model lines up with the picker.
  • Boards within a section keep the existing updated_at DESC order.
  • When a specific workspace is selected, no headers are shown — the flat grid is retained, since the parent is already implied by the filter.

The alternative — a small workspace badge on each card — is what mail clients like Gmail do for labels, but it scales poorly here: workspace names are often long, the grid is dense, and the user usually wants to think about "the boards in workspace X" as a unit.

Affected files

  • crates/hero_whiteboard_ui/templates/web/home.html -- loadBoards() (around lines 240+) is where the rendering happens. The change is contained to this function plus a small CSS tweak in the <style> block at the top.

No server / SDK / openrpc / DB changes. The workspacesCache (already populated by loadWorkspaces) gives us the workspace name from the workspace_id already on each board.

Expected behavior

  • currentWorkspaceId === '' (All Workspaces): boards are grouped by workspace. Each group has a header showing <workspace name> · <count> (e.g. Sprint planning · 4). Sections appear in the same order as the dropdown (most-recently-updated workspace first). Boards inside a section preserve the current updated-at-desc order.
  • currentWorkspaceId set to a real id: the flat grid is rendered, exactly as it does today (no headers).
  • A board whose workspace_id doesn't appear in workspacesCache (e.g. a workspace that was just deleted but the cache is stale) is grouped under a fallback header Workspace #<id> -- never lost.
  • The empty state when zero boards exist is unchanged.

Acceptance criteria

  • When All Workspaces is selected, board cards are grouped under per-workspace section headers showing the workspace name and the board count.
  • Section order matches the workspace filter dropdown order.
  • Within a section, board cards preserve the existing updated_at DESC order.
  • When a specific workspace is selected, the grid is rendered flat (no headers) -- behavior unchanged.
  • The empty state when there are zero boards is unchanged.
  • Boards whose workspace_id is not in workspacesCache are grouped under a Workspace #<id> fallback header at the end of the list.
  • No regression in the per-card actions (share / rename / duplicate / delete).
  • Layout works in both light and dark themes (var(--wb-...) tokens only).
  • Diff stays inside crates/hero_whiteboard_ui/templates/web/home.html.
  • cargo check --workspace, cargo clippy --workspace -- -D warnings, cargo fmt --all -- --check, cargo test --workspace all pass.

Notes

  • Reuse the existing .board-grid class for the per-section grid so the card layout (column count, gap, hover affordance) is unchanged. Add a small wrapper class (e.g. .workspace-section) and a header class (e.g. .workspace-section-header).
  • The header should be visually quiet -- 13px medium, var(--wb-text-muted) with the count, top margin of ~16px between sections. Match the styling of the existing Loading boards... placeholder so it doesn't compete with the navbar.
  • No need to make sections collapsible in this iteration; keep scope tight. A follow-up can add collapse-state persistence if it turns out users want it.
  • Loading the workspaces and the boards in parallel is fine -- both loadWorkspaces and loadBoards already run concurrently from DOMContentLoaded. If workspacesCache happens to be empty when loadBoards runs first (race), the fallback Workspace #<id> headers cover the gap and the next render after loadWorkspaces finishes will resolve the names. To avoid the flicker, loadBoards() can await loadWorkspaces() first when the cache is empty -- the spec should pick one approach and document it.
## Improvement When the workspace filter dropdown is on `All Workspaces`, the home page renders every board in a single flat grid sorted by recency. There is no visual cue showing which workspace each board belongs to. As soon as a user has more than a couple of workspaces, scanning the grid becomes confusing — two boards with similar names from different workspaces look identical, and there's no way to mentally group what you're seeing. When a specific workspace is selected the grid is fine; the issue only appears for the "all" view. ## Best practice Products in the same space (Miro, Trello, Linear, Notion's database views, GitHub project boards) all solve this the same way: when listing entities across multiple parents, group by parent with section headers. Specifically: - A small section header per workspace (workspace name + live board count). - Boards rendered in a sub-grid under each header. - Sections ordered the same way the workspace dropdown is ordered (most recently updated first), so the user's mental model lines up with the picker. - Boards within a section keep the existing `updated_at DESC` order. - When a specific workspace is selected, no headers are shown — the flat grid is retained, since the parent is already implied by the filter. The alternative — a small workspace badge on each card — is what mail clients like Gmail do for labels, but it scales poorly here: workspace names are often long, the grid is dense, and the user usually wants to think about "the boards in workspace X" as a unit. ## Affected files - `crates/hero_whiteboard_ui/templates/web/home.html` -- `loadBoards()` (around lines 240+) is where the rendering happens. The change is contained to this function plus a small CSS tweak in the `<style>` block at the top. No server / SDK / openrpc / DB changes. The `workspacesCache` (already populated by `loadWorkspaces`) gives us the workspace name from the `workspace_id` already on each board. ## Expected behavior - `currentWorkspaceId === ''` (All Workspaces): boards are grouped by workspace. Each group has a header showing `<workspace name> · <count>` (e.g. `Sprint planning · 4`). Sections appear in the same order as the dropdown (most-recently-updated workspace first). Boards inside a section preserve the current updated-at-desc order. - `currentWorkspaceId` set to a real id: the flat grid is rendered, exactly as it does today (no headers). - A board whose `workspace_id` doesn't appear in `workspacesCache` (e.g. a workspace that was just deleted but the cache is stale) is grouped under a fallback header `Workspace #<id>` -- never lost. - The empty state when zero boards exist is unchanged. ## Acceptance criteria - [ ] When `All Workspaces` is selected, board cards are grouped under per-workspace section headers showing the workspace name and the board count. - [ ] Section order matches the workspace filter dropdown order. - [ ] Within a section, board cards preserve the existing `updated_at DESC` order. - [ ] When a specific workspace is selected, the grid is rendered flat (no headers) -- behavior unchanged. - [ ] The empty state when there are zero boards is unchanged. - [ ] Boards whose `workspace_id` is not in `workspacesCache` are grouped under a `Workspace #<id>` fallback header at the end of the list. - [ ] No regression in the per-card actions (share / rename / duplicate / delete). - [ ] Layout works in both light and dark themes (`var(--wb-...)` tokens only). - [ ] Diff stays inside `crates/hero_whiteboard_ui/templates/web/home.html`. - [ ] `cargo check --workspace`, `cargo clippy --workspace -- -D warnings`, `cargo fmt --all -- --check`, `cargo test --workspace` all pass. ## Notes - Reuse the existing `.board-grid` class for the per-section grid so the card layout (column count, gap, hover affordance) is unchanged. Add a small wrapper class (e.g. `.workspace-section`) and a header class (e.g. `.workspace-section-header`). - The header should be visually quiet -- 13px medium, `var(--wb-text-muted)` with the count, top margin of ~16px between sections. Match the styling of the existing `Loading boards...` placeholder so it doesn't compete with the navbar. - No need to make sections collapsible in this iteration; keep scope tight. A follow-up can add collapse-state persistence if it turns out users want it. - Loading the workspaces and the boards in parallel is fine -- both `loadWorkspaces` and `loadBoards` already run concurrently from `DOMContentLoaded`. If `workspacesCache` happens to be empty when `loadBoards` runs first (race), the fallback `Workspace #<id>` headers cover the gap and the next render after `loadWorkspaces` finishes will resolve the names. To avoid the flicker, `loadBoards()` can `await loadWorkspaces()` first when the cache is empty -- the spec should pick one approach and document it.
Author
Member

Implementation Spec for Issue #86

Objective

When the workspace filter on the home page is All Workspaces, render boards grouped by workspace under section headers; when a specific workspace is selected, keep the current flat grid.

Requirements

  • Section header per workspace showing <workspace name> · <count>.
  • Sections ordered the same way the workspace dropdown is ordered (workspace.list returns updated_at DESC).
  • Boards within a section preserve the existing updated_at DESC order from board.list.
  • When a specific workspace is selected, the grid is rendered flat (no headers) -- behavior unchanged.
  • Boards whose workspace_id is not in workspacesCache are grouped under Workspace #<id> at the end.
  • Empty-state (zero boards) is unchanged.
  • Per-card actions (share / rename / duplicate / delete) are unchanged.
  • var(--wb-...) tokens only — works in light + dark themes.
  • Diff is contained to crates/hero_whiteboard_ui/templates/web/home.html.
  • cargo check / clippy / fmt --check / test clean.

Files to Modify

  • crates/hero_whiteboard_ui/templates/web/home.htmlloadBoards() and a small CSS addition in {% block head %}.

No server / SDK / openrpc / DB changes; workspacesCache already gives the UI everything it needs.

Implementation Plan

Step 1: Group boards by workspace in loadBoards and add section-header CSS

Files: crates/hero_whiteboard_ui/templates/web/home.html

  1. Extract the per-card render into a small inner helper renderBoardCard(board, gridEl) that builds the card and appends it to a passed-in grid element. Today the loop builds the card inline and appends it to grid; pulling the inner block into a helper keeps both code paths (flat and grouped) readable.

  2. Make the workspaces cache available before the first render. The cleanest fix is to await loadWorkspaces() from inside loadBoards() when workspacesCache is empty. The DOMContentLoaded init already calls both, but races aren't guaranteed to favor workspaces-first. Adding the await in loadBoards is the smallest change and avoids the Workspace #<id> flicker.

  3. After the existing boards fetch, branch on currentWorkspaceId:

    if (currentWorkspaceId) {
        // Single-workspace view — flat grid, behavior unchanged.
        grid.innerHTML = '';
        boards.forEach(function(b) { renderBoardCard(b, grid); });
        return;
    }
    // All-Workspaces view — group by workspace_id.
    var groups = {};                      // ws_id -> { name, boards: [] }
    var orphan = { name: '', boards: [] };// boards whose ws isn't cached
    var nameById = {};
    for (var i = 0; i < workspacesCache.length; i++) {
        nameById[String(workspacesCache[i].id)] = workspacesCache[i].name || String(workspacesCache[i].id);
    }
    boards.forEach(function(b) {
        var key = String(b.workspace_id);
        if (!nameById[key]) {
            orphan.boards.push(b);
            return;
        }
        if (!groups[key]) groups[key] = { name: nameById[key], boards: [] };
        groups[key].boards.push(b);
    });
    
  4. Build the rendered output. Iterate workspacesCache in order so section order matches the dropdown. For each workspace that has boards, render a .workspace-section containing a .workspace-section-header and a fresh .board-grid; the helper appends each card to that grid. Append orphan groups at the end (one per distinct id) with the fallback name Workspace #<id>.

    grid.innerHTML = '';
    grid.classList.add('grouped'); // optional CSS hook
    var wrapper = document.createDocumentFragment();
    workspacesCache.forEach(function(ws) {
        var key = String(ws.id);
        var g = groups[key];
        if (!g || !g.boards.length) return;
        wrapper.appendChild(renderSection(g.name, g.boards));
    });
    // Orphans at the end, one section per unique id.
    var orphanGroups = {};
    orphan.boards.forEach(function(b) {
        var k = String(b.workspace_id);
        if (!orphanGroups[k]) orphanGroups[k] = [];
        orphanGroups[k].push(b);
    });
    Object.keys(orphanGroups).forEach(function(k) {
        wrapper.appendChild(renderSection('Workspace #' + k, orphanGroups[k]));
    });
    grid.appendChild(wrapper);
    
    function renderSection(title, boards) {
        var section = document.createElement('div');
        section.className = 'workspace-section';
        var h = document.createElement('div');
        h.className = 'workspace-section-header';
        h.textContent = title + ' · ' + boards.length;
        section.appendChild(h);
        var sub = document.createElement('div');
        sub.className = 'board-grid';
        boards.forEach(function(b) { renderBoardCard(b, sub); });
        section.appendChild(sub);
        return section;
    }
    
  5. CSS — add a small block in the existing {% block head %} <style>:

    .workspace-section { margin-top: 16px; }
    .workspace-section:first-child { margin-top: 0; }
    .workspace-section-header {
        margin: 0 0 8px;
        padding: 0 4px;
        font-size: 13px;
        font-weight: 500;
        color: var(--wb-text-muted);
    }
    .board-grid.grouped { /* let inner .board-grid handle the columns */ }
    

    Reuse the existing .board-grid class for the inner grids so the column / gap / hover behavior is identical.

  6. Empty state — keep the existing boards.length === 0 early-return that renders the centered No boards yet placeholder. That still goes inside the outer #board-grid and shows whether we're filtered or not.

Dependencies: none.

Acceptance Criteria

  • When All Workspaces is selected, boards are grouped under per-workspace section headers (<name> · <count>).
  • Section order matches the workspace filter dropdown order.
  • Within a section, board cards keep the existing updated_at DESC order.
  • When a specific workspace is selected, the grid is flat (no headers) — unchanged from today.
  • Boards whose workspace_id is not in workspacesCache are grouped under Workspace #<id> at the end.
  • Empty state when zero boards exist is unchanged.
  • Per-card actions still work.
  • Light + dark themes both look correct.
  • Diff is contained to crates/hero_whiteboard_ui/templates/web/home.html.
  • cargo check / clippy / fmt --check / test all pass.

Notes

  • loadBoards() should await loadWorkspaces() when the cache is empty, so the first render after a hard refresh already has names. After that the cache is populated and subsequent renders are instant.
  • Use document.createElement / appendChild in this branch — building the outer DOM via string concatenation gets messy with nested grids. The per-card render can keep using card.innerHTML = '...' as it does today.
  • The fallback for stale-cache workspaces (Workspace #<id>) is rare in practice but cheap to handle and avoids dropping boards from the view.
  • Don't make sections collapsible in this iteration; if we want it later it's easy to add a <details> wrapper around each section.
## Implementation Spec for Issue #86 ### Objective When the workspace filter on the home page is `All Workspaces`, render boards grouped by workspace under section headers; when a specific workspace is selected, keep the current flat grid. ### Requirements - Section header per workspace showing `<workspace name> · <count>`. - Sections ordered the same way the workspace dropdown is ordered (`workspace.list` returns `updated_at DESC`). - Boards within a section preserve the existing `updated_at DESC` order from `board.list`. - When a specific workspace is selected, the grid is rendered flat (no headers) -- behavior unchanged. - Boards whose `workspace_id` is not in `workspacesCache` are grouped under `Workspace #<id>` at the end. - Empty-state (zero boards) is unchanged. - Per-card actions (share / rename / duplicate / delete) are unchanged. - `var(--wb-...)` tokens only — works in light + dark themes. - Diff is contained to `crates/hero_whiteboard_ui/templates/web/home.html`. - `cargo check / clippy / fmt --check / test` clean. ### Files to Modify - `crates/hero_whiteboard_ui/templates/web/home.html` — `loadBoards()` and a small CSS addition in `{% block head %}`. No server / SDK / openrpc / DB changes; `workspacesCache` already gives the UI everything it needs. ### Implementation Plan #### Step 1: Group boards by workspace in `loadBoards` and add section-header CSS Files: `crates/hero_whiteboard_ui/templates/web/home.html` 1. Extract the per-card render into a small inner helper `renderBoardCard(board, gridEl)` that builds the card and appends it to a passed-in grid element. Today the loop builds the card inline and appends it to `grid`; pulling the inner block into a helper keeps both code paths (flat and grouped) readable. 2. Make the workspaces cache available before the first render. The cleanest fix is to `await loadWorkspaces()` from inside `loadBoards()` when `workspacesCache` is empty. The DOMContentLoaded init already calls both, but races aren't guaranteed to favor workspaces-first. Adding the await in `loadBoards` is the smallest change and avoids the `Workspace #<id>` flicker. 3. After the existing `boards` fetch, branch on `currentWorkspaceId`: ```js if (currentWorkspaceId) { // Single-workspace view — flat grid, behavior unchanged. grid.innerHTML = ''; boards.forEach(function(b) { renderBoardCard(b, grid); }); return; } // All-Workspaces view — group by workspace_id. var groups = {}; // ws_id -> { name, boards: [] } var orphan = { name: '', boards: [] };// boards whose ws isn't cached var nameById = {}; for (var i = 0; i < workspacesCache.length; i++) { nameById[String(workspacesCache[i].id)] = workspacesCache[i].name || String(workspacesCache[i].id); } boards.forEach(function(b) { var key = String(b.workspace_id); if (!nameById[key]) { orphan.boards.push(b); return; } if (!groups[key]) groups[key] = { name: nameById[key], boards: [] }; groups[key].boards.push(b); }); ``` 4. Build the rendered output. Iterate `workspacesCache` in order so section order matches the dropdown. For each workspace that has boards, render a `.workspace-section` containing a `.workspace-section-header` and a fresh `.board-grid`; the helper appends each card to that grid. Append orphan groups at the end (one per distinct id) with the fallback name `Workspace #<id>`. ```js grid.innerHTML = ''; grid.classList.add('grouped'); // optional CSS hook var wrapper = document.createDocumentFragment(); workspacesCache.forEach(function(ws) { var key = String(ws.id); var g = groups[key]; if (!g || !g.boards.length) return; wrapper.appendChild(renderSection(g.name, g.boards)); }); // Orphans at the end, one section per unique id. var orphanGroups = {}; orphan.boards.forEach(function(b) { var k = String(b.workspace_id); if (!orphanGroups[k]) orphanGroups[k] = []; orphanGroups[k].push(b); }); Object.keys(orphanGroups).forEach(function(k) { wrapper.appendChild(renderSection('Workspace #' + k, orphanGroups[k])); }); grid.appendChild(wrapper); function renderSection(title, boards) { var section = document.createElement('div'); section.className = 'workspace-section'; var h = document.createElement('div'); h.className = 'workspace-section-header'; h.textContent = title + ' · ' + boards.length; section.appendChild(h); var sub = document.createElement('div'); sub.className = 'board-grid'; boards.forEach(function(b) { renderBoardCard(b, sub); }); section.appendChild(sub); return section; } ``` 5. **CSS** — add a small block in the existing `{% block head %}` `<style>`: ```css .workspace-section { margin-top: 16px; } .workspace-section:first-child { margin-top: 0; } .workspace-section-header { margin: 0 0 8px; padding: 0 4px; font-size: 13px; font-weight: 500; color: var(--wb-text-muted); } .board-grid.grouped { /* let inner .board-grid handle the columns */ } ``` Reuse the existing `.board-grid` class for the inner grids so the column / gap / hover behavior is identical. 6. **Empty state** — keep the existing `boards.length === 0` early-return that renders the centered `No boards yet` placeholder. That still goes inside the outer `#board-grid` and shows whether we're filtered or not. Dependencies: none. ### Acceptance Criteria - [ ] When `All Workspaces` is selected, boards are grouped under per-workspace section headers (`<name> · <count>`). - [ ] Section order matches the workspace filter dropdown order. - [ ] Within a section, board cards keep the existing `updated_at DESC` order. - [ ] When a specific workspace is selected, the grid is flat (no headers) — unchanged from today. - [ ] Boards whose `workspace_id` is not in `workspacesCache` are grouped under `Workspace #<id>` at the end. - [ ] Empty state when zero boards exist is unchanged. - [ ] Per-card actions still work. - [ ] Light + dark themes both look correct. - [ ] Diff is contained to `crates/hero_whiteboard_ui/templates/web/home.html`. - [ ] `cargo check / clippy / fmt --check / test` all pass. ### Notes - `loadBoards()` should `await loadWorkspaces()` when the cache is empty, so the first render after a hard refresh already has names. After that the cache is populated and subsequent renders are instant. - Use `document.createElement` / `appendChild` in this branch — building the outer DOM via string concatenation gets messy with nested grids. The per-card render can keep using `card.innerHTML = '...'` as it does today. - The fallback for stale-cache workspaces (`Workspace #<id>`) is rare in practice but cheap to handle and avoids dropping boards from the view. - Don't make sections collapsible in this iteration; if we want it later it's easy to add a `<details>` wrapper around each section.
Author
Member

Test Results

  • cargo test --workspace: all green (no test changes needed; this is a UI-only behavior).
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.

Manual verification recommended:

  1. Open the home page with All Workspaces selected — boards appear under per-workspace section headers (<workspace name> · <count>).
  2. Section order matches the order of the workspace dropdown.
  3. Within each section, boards are in updated_at DESC order.
  4. Pick a specific workspace from the dropdown — the headers disappear and the grid is flat (single workspace).
  5. Switch back to All Workspaces — grouped layout returns immediately.
  6. Empty state (No boards yet) renders correctly.
  7. Per-card actions (share / rename / duplicate / delete) work the same in both modes.
  8. Light + dark themes both look correct.
  9. If a board's workspace_id isn't in the cache, it shows up under a Workspace #<id> section at the end.
## Test Results - `cargo test --workspace`: all green (no test changes needed; this is a UI-only behavior). - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. Manual verification recommended: 1. Open the home page with `All Workspaces` selected — boards appear under per-workspace section headers (`<workspace name> · <count>`). 2. Section order matches the order of the workspace dropdown. 3. Within each section, boards are in `updated_at DESC` order. 4. Pick a specific workspace from the dropdown — the headers disappear and the grid is flat (single workspace). 5. Switch back to `All Workspaces` — grouped layout returns immediately. 6. Empty state (`No boards yet`) renders correctly. 7. Per-card actions (share / rename / duplicate / delete) work the same in both modes. 8. Light + dark themes both look correct. 9. If a board's `workspace_id` isn't in the cache, it shows up under a `Workspace #<id>` section at the end.
Author
Member

Implementation Summary

1 file changed, +105 / -27 — all in crates/hero_whiteboard_ui/templates/web/home.html.

Changes

  • Extracted the per-card render into renderBoardCard(board, gridEl) and added a thin renderBoardSection(title, boards) helper.
  • loadBoards now branches on the filter:
    • Specific workspace selected: removes the grouped class from #board-grid and renders a flat grid (behavior unchanged).
    • All Workspaces: adds grouped to #board-grid, builds a stack of .workspace-section elements (header + inner .board-grid) ordered by workspacesCache. Boards whose workspace isn't in the cache fall through to Workspace #<id> orphan sections at the end.
  • loadBoards awaits loadWorkspaces() when the cache is empty, so the first render after a hard refresh already has workspace names (no flicker / no fallback labels for normal cases).
  • Added scoped CSS for the grouped mode: #board-grid.grouped becomes a vertical stack (no inner column grid), each .workspace-section carries the horizontal padding that the flat grid normally would, and the inner per-section .board-grid keeps its column layout but loses its outer padding so the header aligns with the cards.
  • The No boards yet empty state explicitly removes the grouped class so the grid-column:1/-1 layout still works.

Verification

  • cargo test --workspace: all green.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.

Notes / caveats

  • UI-only change. No server / SDK / openrpc / DB changes; the workspacesCache already populated by loadWorkspaces is the only source of truth for the section names.
  • Section order matches the dropdown order (workspace.list returns updated_at DESC).
  • The implementation does not make sections collapsible. If we want that later it's a small follow-up: wrap each .workspace-section in a <details> and persist the open/closed state per workspace id.
## Implementation Summary 1 file changed, +105 / -27 — all in `crates/hero_whiteboard_ui/templates/web/home.html`. ### Changes - Extracted the per-card render into `renderBoardCard(board, gridEl)` and added a thin `renderBoardSection(title, boards)` helper. - `loadBoards` now branches on the filter: - **Specific workspace selected**: removes the `grouped` class from `#board-grid` and renders a flat grid (behavior unchanged). - **All Workspaces**: adds `grouped` to `#board-grid`, builds a stack of `.workspace-section` elements (header + inner `.board-grid`) ordered by `workspacesCache`. Boards whose workspace isn't in the cache fall through to `Workspace #<id>` orphan sections at the end. - `loadBoards` `await`s `loadWorkspaces()` when the cache is empty, so the first render after a hard refresh already has workspace names (no flicker / no fallback labels for normal cases). - Added scoped CSS for the grouped mode: `#board-grid.grouped` becomes a vertical stack (no inner column grid), each `.workspace-section` carries the horizontal padding that the flat grid normally would, and the inner per-section `.board-grid` keeps its column layout but loses its outer padding so the header aligns with the cards. - The `No boards yet` empty state explicitly removes the `grouped` class so the `grid-column:1/-1` layout still works. ### Verification - `cargo test --workspace`: all green. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. ### Notes / caveats - UI-only change. No server / SDK / openrpc / DB changes; the `workspacesCache` already populated by `loadWorkspaces` is the only source of truth for the section names. - Section order matches the dropdown order (`workspace.list` returns `updated_at DESC`). - The implementation does not make sections collapsible. If we want that later it's a small follow-up: wrap each `.workspace-section` in a `<details>` and persist the open/closed state per workspace id.
Sign in to join this conversation.
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
lhumina_code/hero_whiteboard#86
No description provided.