Turn the header into a sidebar from 960px up #240

Merged
Grandiras merged 4 commits from ui-v2-sidebar into main 2026-10-03 00:19:42 +00:00
Owner

The UI language v2 sidebar, signed off by the owner, on today's pages. It is the first structural piece after the foundations (#238).

What changes

  • From 960px the header is a sidebar: a sheet of material floating on the left edge, holding:
    • the wordmark;
    • the search field (its result panel is 34rem wide and spills over the page);
    • Home and Calendar;
    • the boards, one row each in the board's own colour and lit when open. Like the switcher, a row is the same page on that board, so a calendar stays a calendar;
    • the account at the foot, whose menu opens upwards.
  • 600–959px keeps the top bar and the board switcher, now with Home and Calendar as links. Phones are unchanged.
  • A board's views move into a title bar (BoardTitleBar) on the board, its calendar and its settings pages from 600px: the board's name, Board / Calendar as a segmented control, and Settings for its admins. The header lost them when it became a sidebar of destinations. Each page passes its own admin check, so NavHeader no longer loads the administered boards, and the phone-viewport watcher only it used is removed.
  • Heights are tokens (--header-h, --titlebar-h, --sidebar-w). The board, the calendar and the empty states subtract what is actually there, and the toasts centre beside the sidebar.
  • docs/ui.md gains The sidebar and a board's title bar. search.md, calendar.md and boards-and-permissions.md are updated. There is a changelog line in both locales.

Specs

  • Specs that used the header's views or the switcher to get around now use the sidebar and the title bar.
  • The switcher's own behaviour stays pinned at 820px, where it lives.
  • calendar-all.spec.js gains the sidebar's way in and out of the general calendar.
  • search.spec.js trades "centred in the header" for the field fitting the sidebar and its panel spilling over the page inside the window.
  • board.spec.js polls the column count, because the circuit's first render briefly swaps the prerendered columns for the loading state. That happens on main too.

Checked

  • tools/verify.sh passes (584 unit tests).
  • The full e2e suite on an isolated stack: 157/157 (156 + 1 new).
  • Screenshots in light and dark at 1440 / 820 / 390 of the board, card dialog, Home, both calendars, every settings tab, Members, Profile, the menu and search.

Not in this PR (from the draft): a "New board" row in the sidebar and card counts beside board names. Counts would add a query to every page's header.

🤖 Generated with Claude Code

The UI language v2 **sidebar**, signed off by the owner, on today's pages. It is the first structural piece after the foundations (#238). **What changes** - **From 960px the header is a sidebar:** a sheet of material floating on the left edge, holding: - the wordmark; - the search field (its result panel is 34rem wide and spills over the page); - Home and Calendar; - the boards, one row each in the board's own colour and lit when open. Like the switcher, a row is the same page on that board, so a calendar stays a calendar; - the account at the foot, whose menu opens upwards. - **600–959px** keeps the top bar and the board switcher, now with Home and Calendar as links. **Phones are unchanged.** - **A board's views move into a title bar** (`BoardTitleBar`) on the board, its calendar and its settings pages from 600px: the board's name, Board / Calendar as a segmented control, and Settings for its admins. The header lost them when it became a sidebar of destinations. Each page passes its own admin check, so `NavHeader` no longer loads the administered boards, and the phone-viewport watcher only it used is removed. - **Heights are tokens** (`--header-h`, `--titlebar-h`, `--sidebar-w`). The board, the calendar and the empty states subtract what is actually there, and the toasts centre beside the sidebar. - `docs/ui.md` gains *The sidebar and a board's title bar*. `search.md`, `calendar.md` and `boards-and-permissions.md` are updated. There is a changelog line in both locales. **Specs** - Specs that used the header's views or the switcher to get around now use the sidebar and the title bar. - The switcher's own behaviour stays pinned at 820px, where it lives. - `calendar-all.spec.js` gains the sidebar's way in and out of the general calendar. - `search.spec.js` trades "centred in the header" for the field fitting the sidebar and its panel spilling over the page inside the window. - `board.spec.js` polls the column count, because the circuit's first render briefly swaps the prerendered columns for the loading state. That happens on main too. **Checked** - `tools/verify.sh` passes (584 unit tests). - The full e2e suite on an isolated stack: **157/157** (156 + 1 new). - Screenshots in light and dark at 1440 / 820 / 390 of the board, card dialog, Home, both calendars, every settings tab, Members, Profile, the menu and search. Not in this PR (from the draft): a "New board" row in the sidebar and card counts beside board names. Counts would add a query to every page's header. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The v2 language's sidebar, on today's pages. From 960px the header is a sheet
of material on the left edge: the wordmark, the search, Home and Calendar, the
boards (each row the same page on that board, lit in its own colour) and the
account at the foot, whose menu opens upwards. Between 600 and 959px it stays
a top bar, now with Home and Calendar as links; a phone keeps its bottom bar.

A board's views (Board, Calendar, Settings) move from the header into a title
bar at the top of its pages, BoardTitleBar, with the board's name. The header
therefore no longer loads which boards the reader administers, and the phone
viewport watcher it alone used is gone.

The header's height and the title bar's are tokens now (--header-h,
--titlebar-h, --sidebar-w), so the board, the calendar and the empty states
subtract what is actually there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow the sidebar in the e2e specs and the docs
All checks were successful
CI / build (pull_request) Successful in 42s
3f9d97c960
The specs that used the header's views or the switcher to get somewhere now
use the sidebar and the title bar. The switcher's own behaviour (the same page
on another board, "All boards" on a calendar) is still pinned, at 820px where
it lives, and calendar-all.spec.js gains the sidebar's way in and out of the
general calendar. search.spec.js trades "centred in the header" for the field
fitting the sidebar and its panel spilling over the page inside the window.

board.spec.js polls the column count: the circuit's first render briefly
swaps the prerendered columns for the loading state, on main too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-240/run-2026-10-03T00-06-59-776Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/671); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-240/run-671.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-03 00:07:31 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

The change looks good to merge. I read the diff and spot-checked two things: Calendar.razor's new did use and the removed phone-viewport watcher. I did not build the project or run any tests.

  • Calendar.razor: _canManageBoard is computed inside ReloadAsync with the did already in scope, and it is re-asked on every reload.
  • Removed code: a grep found no remaining uses of watchPhoneViewport or OnViewportChanged.
  • Docs, changelog, specs: the docs, both changelog locales and the spec updates are consistent with the code.

One minor leftover, not blocking: app.css (around line 6385) still has .nav-admin-links { display: none; }. The markup no longer uses that class, so the rule is dead and can go.


🧠 claude-sonnet-5-5 · ⏱ 0m 18s · 4 turns · tokens: 41.0k in / 942 out (+114.4k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. The change looks good to merge. I read the diff and spot-checked two things: `Calendar.razor`'s new `did` use and the removed phone-viewport watcher. I did not build the project or run any tests. - **`Calendar.razor`:** `_canManageBoard` is computed inside `ReloadAsync` with the `did` already in scope, and it is re-asked on every reload. - **Removed code:** a grep found no remaining uses of `watchPhoneViewport` or `OnViewportChanged`. - **Docs, changelog, specs:** the docs, both changelog locales and the spec updates are consistent with the code. One minor leftover, not blocking: `app.css` (around line 6385) still has `.nav-admin-links { display: none; }`. The markup no longer uses that class, so the rule is dead and can go. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 18s · 4 turns · tokens: 41.0k in / 942 out (+114.4k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-240/run-671.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:3f9d97c96024db15d77de0cb49e4e0451d3ba4fb -->
opencode-reviewer approved these changes 2026-10-03 00:16:33 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

A coherent, well-documented structural change; the risky parts check out.

What I verified:

  • Calendar.razor's _canManageBoard: computed inside ReloadAsync, which OnParametersSetAsync runs on every board-to-board and all⇄board navigation, and again via the BoardChanged/BoardsChanged events — so the title bar's Settings link can't go stale across board reuse. Stale-after-navigation is at most one frame and the target page re-checks server-side; nothing security-relevant hangs on it.
  • Persisted-state shape change (CarriedState → CarriedBoard[], same key): an old persisted blob fails TryTakeFromJson<CarriedBoard[]> and falls back to LoadBoardsAsync — safe, transient.
  • Removing the viewport watcher: watchPhoneViewport/unwatchPhoneViewport/OnViewportChanged have no remaining references, and nothing else needed _isPhone (it only gated the now-deleted .nav-admin-links). _selfRef/_prefsModule stay live for install/changelog.
  • Height tokens: phone keeps its own overrides (.board subtracts --phone-nav-h/--phone-board-title-h, .empty-state likewise), --titlebar-h is only set ≥600px via body:has(), and at ≥960px --header-h: 0 matches the header leaving flow as a fixed sheet — the board, calendar and empty-state arithmetic adds up at all three widths.
  • The two-light scheme: .nav-tab-active is only styled in the phone media, so tablet/desktop rely solely on .nav-tab-here, exactly as docs/ui.md now says.
  • Token re-derivation: the sidebar rows' inline --board-color is consumed by the existing [style*="--board-color"] block, so each row's active state and mark wear their own board's colour, overriding the page tint from BoardTint.
  • Search panel: at ≥960px it keeps left: 0 from the base rule and drops right, so it extends 34rem from the sidebar's field across the page — matching the new spec assertions.
  • Specs: BOARD_KEY is a key-shape regex, so the "every row is a board page" loops in boards.spec.js correctly assert shape, not a specific board; the removed openSwitcher in access.spec.js leaves no dangling references (calendar-all defines its own, still used by the new 820px block).

Minor, non-blocking: the phone media still carries a now-dead .nav-admin-links { display: none; } rule (~app.css:6385) — the class no longer exists in any markup; and each board page now has two top-level h1s (the wordmark and .board-titlebar-name), which is valid HTML but a heading-hierarchy nit if you ever care to demote the wordmark.


⏱ 9m 34s · 22 steps · $0.0883 · tokens: 201.2k in / 2.9k out (+1271.4k cache-read) · Go: 5h 2% · wk 8% · mo 86%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. A coherent, well-documented structural change; the risky parts check out. **What I verified:** - **`Calendar.razor`'s `_canManageBoard`**: computed inside `ReloadAsync`, which `OnParametersSetAsync` runs on every board-to-board and all⇄board navigation, and again via the `BoardChanged`/`BoardsChanged` events — so the title bar's Settings link can't go stale across board reuse. Stale-after-navigation is at most one frame and the target page re-checks server-side; nothing security-relevant hangs on it. - **Persisted-state shape change** (`CarriedState` → `CarriedBoard[]`, same key): an old persisted blob fails `TryTakeFromJson<CarriedBoard[]>` and falls back to `LoadBoardsAsync` — safe, transient. - **Removing the viewport watcher**: `watchPhoneViewport`/`unwatchPhoneViewport`/`OnViewportChanged` have no remaining references, and nothing else needed `_isPhone` (it only gated the now-deleted `.nav-admin-links`). `_selfRef`/`_prefsModule` stay live for install/changelog. - **Height tokens**: phone keeps its own overrides (`.board` subtracts `--phone-nav-h`/`--phone-board-title-h`, `.empty-state` likewise), `--titlebar-h` is only set ≥600px via `body:has()`, and at ≥960px `--header-h: 0` matches the header leaving flow as a fixed sheet — the board, calendar and empty-state arithmetic adds up at all three widths. - **The two-light scheme**: `.nav-tab-active` is only styled in the phone media, so tablet/desktop rely solely on `.nav-tab-here`, exactly as `docs/ui.md` now says. - **Token re-derivation**: the sidebar rows' inline `--board-color` is consumed by the existing `[style*="--board-color"]` block, so each row's active state and mark wear their own board's colour, overriding the page tint from `BoardTint`. - **Search panel**: at ≥960px it keeps `left: 0` from the base rule and drops `right`, so it extends 34rem from the sidebar's field across the page — matching the new spec assertions. - **Specs**: `BOARD_KEY` is a key-shape regex, so the "every row is a board page" loops in `boards.spec.js` correctly assert shape, not a specific board; the removed `openSwitcher` in `access.spec.js` leaves no dangling references (calendar-all defines its own, still used by the new 820px block). Minor, non-blocking: the phone media still carries a now-dead `.nav-admin-links { display: none; }` rule (~app.css:6385) — the class no longer exists in any markup; and each board page now has two top-level `h1`s (the wordmark and `.board-titlebar-name`), which is valid HTML but a heading-hierarchy nit if you ever care to demote the wordmark. --- ⏱ 9m 34s · 22 steps · $0.0883 · tokens: 201.2k in / 2.9k out (+1271.4k cache-read) · Go: 5h 2% · wk 8% · mo 86% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-240/run-2026-10-03T00-06-59-776Z.md) <!-- opencode-reviewed-head:3f9d97c96024db15d77de0cb49e4e0451d3ba4fb --> <!-- opencode-verdict:approve -->
@ -4908,25 +4917,61 @@ td.admin-row-actions button + button {
/* ── Nav links (views + admin) ── */
.nav-admin-links {
Collaborator

The markup-side removal is complete, but the phone media still carries a rule for the deleted class: .nav-admin-links { display: none; } at app.css:6385 is now dead code. Worth dropping in a follow-up (or here) so the next reader doesn't go looking for the markup it once hid.

The markup-side removal is complete, but the phone media still carries a rule for the deleted class: `.nav-admin-links { display: none; }` at app.css:6385 is now dead code. Worth dropping in a follow-up (or here) so the next reader doesn't go looking for the markup it once hid.
Author
Owner

Thanks both. Dropped the dead .nav-admin-links rule. On the two h1s: the wordmark has been an h1 on every page since before this PR, and .board-header h1 is styled and measured in several places, so demoting it is a separate tidy-up rather than part of the sidebar.

Thanks both. Dropped the dead `.nav-admin-links` rule. On the two `h1`s: the wordmark has been an `h1` on every page since before this PR, and `.board-header h1` is styled and measured in several places, so demoting it is a separate tidy-up rather than part of the sidebar.
Drop the phone rule for the header's old view links
All checks were successful
CI / build (pull_request) Successful in 40s
5fb55e5003
Both reviews: .nav-admin-links left the markup with the sidebar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-240/run-2026-10-03T00-18-15-436Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/672); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-240/run-672.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

My one earlier note is resolved. The dead .nav-admin-links { display: none; } rule in the phone block is now deleted in the diff, along with the base .nav-admin-links rule. I re-read the diff only; I did not build or run any tests.

Nothing else new stands out. Calendar.razor computes the title bar's admin check on each reload. The phone-viewport watcher is gone from both the JS and the component.


🧠 claude-sonnet-5-5 · ⏱ 0m 3s · 2 turns · tokens: 39.1k in / 349 out (+12.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. My one earlier note is resolved. The dead `.nav-admin-links { display: none; }` rule in the phone block is now deleted in the diff, along with the base `.nav-admin-links` rule. I re-read the diff only; I did not build or run any tests. Nothing else new stands out. `Calendar.razor` computes the title bar's admin check on each reload. The phone-viewport watcher is gone from both the JS and the component. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 3s · 2 turns · tokens: 39.1k in / 349 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-240/run-672.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:5fb55e5003f44ef0d065ce1119f493b2998ec85c -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. This re-review confirms the earlier pass's notes are addressed: the dead .nav-admin-links { display: none; } rule was removed from the phone media block, and no references to watchPhoneViewport/OnViewportChanged survive anywhere in src/. The structural pieces still hold up on the updated tree: Calendar.razor re-asks _canManageBoard on every reload; the persisted-state shape change degrades safely to LoadBoardsAsync; the height-token arithmetic adds up at 390/820/1440; each sidebar row re-derives its board's colour tokens from the inline --board-color; and BoardHref/ActiveSection yield the "same page on that board" targets the specs assert. Specs, docs and both changelog locales are consistent with the code. Two non-blocking remarks remain as before: each board page has two h1s (wordmark and .board-titlebar-name), which is valid but a heading-hierarchy nit if you ever want to demote the wordmark; and search.spec.js hard-codes 960 as the right window edge in the spill assertion, which is correct for now but will need touching if the sidebar breakpoint ever moves.


⏱ 0m 46s · 5 steps · $0.0121 · tokens: 61.8k in / 652 out (+68.6k cache-read) · Go: 5h 2% · wk 8% · mo 86%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge. This re-review confirms the earlier pass's notes are addressed: the dead `.nav-admin-links { display: none; }` rule was removed from the phone media block, and no references to `watchPhoneViewport`/`OnViewportChanged` survive anywhere in `src/`. The structural pieces still hold up on the updated tree: `Calendar.razor` re-asks `_canManageBoard` on every reload; the persisted-state shape change degrades safely to `LoadBoardsAsync`; the height-token arithmetic adds up at 390/820/1440; each sidebar row re-derives its board's colour tokens from the inline `--board-color`; and `BoardHref`/`ActiveSection` yield the "same page on that board" targets the specs assert. Specs, docs and both changelog locales are consistent with the code. Two non-blocking remarks remain as before: each board page has two `h1`s (wordmark and `.board-titlebar-name`), which is valid but a heading-hierarchy nit if you ever want to demote the wordmark; and `search.spec.js` hard-codes 960 as the right window edge in the spill assertion, which is correct for now but will need touching if the sidebar breakpoint ever moves. --- ⏱ 0m 46s · 5 steps · $0.0121 · tokens: 61.8k in / 652 out (+68.6k cache-read) · Go: 5h 2% · wk 8% · mo 86% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-240/run-2026-10-03T00-18-15-436Z.md) <!-- opencode-reviewed-head:5fb55e5003f44ef0d065ce1119f493b2998ec85c --> <!-- opencode-verdict:approve -->
Grandiras deleted branch ui-v2-sidebar 2026-10-03 00:19:43 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
5 participants
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
Grandiras/Ponente!240
No description provided.