Back to feed

sledtools/pika branch #55

pika-git-chat-sidebar-2

Refine branch discussion drawer layout

Target branch: master

Merge Commit: c8097a10a91fb58e430fc4a9b6d66b860a78d6f4

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #67 success

2 passed

head e23aa9fc62a10a41aa559f5b0eeb9d14be8f60da · queued 2026-03-25 18:39:04 · 2 lane(s)

queued 7s · ran 24s

check-notifications · success check-agent-contracts · success

Summary

This branch refactors the branch discussion drawer from an inline CSS Grid sidebar layout to a fixed-position overlay panel. The previous approach used a .branch-review-shell wrapper with a two-column grid that expanded to accommodate the sidebar; the new approach removes that wrapper entirely, promotes the discussion sidebar and backdrop to top-level siblings of <main>, and uses CSS position: fixed with CSS custom properties for sizing. The main page content shrinks via max-width and padding-right transitions when the drawer is open, rather than being re-flowed by grid column changes. The JavaScript is updated to toggle classes on the <main> element (now carrying id="branch-page"), and a guard is added to keyboard shortcuts so they do not fire while the user is typing in an input, textarea, or contentEditable element. The corresponding Rust integration test is updated to assert the new element ID.

Tutorial Steps

Replace grid-based shell with fixed-position layout using CSS custom properties

Intent: Move from a two-column CSS Grid wrapper (`.branch-review-shell`) that inline-reflowed page content, to a simpler model where the `<main>` element itself (`branch-page`) shrinks its max-width when the drawer is open, and the sidebar is positioned fixed on the right edge of the viewport.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -168,19 +168,14 @@
-      .branch-review-shell {
-        display: grid;
-        grid-template-columns: minmax(0, 1fr) 0;
-        gap: 1.25rem;
-        margin-top: 1rem;
-        align-items: start;
-        transition: grid-template-columns 180ms ease;
-      }
-      .branch-review-shell.discussion-open {
-        grid-template-columns: minmax(0, 1fr) clamp(320px, 24vw, 440px);
+      .branch-page {
+        --discussion-sidebar-width: clamp(480px, 30vw, 620px);
+        --discussion-sidebar-gap: 1.5rem;
+        transition: max-width 180ms ease, padding-right 180ms ease;
       }
-      .branch-review-content {
-        min-width: 0;
+      .branch-page.discussion-open {
+        max-width: calc(2200px - var(--discussion-sidebar-width) - var(--discussion-sidebar-gap));
+        padding-right: 0.5rem;
       }

The old layout used a .branch-review-shell div with display: grid and two columns. When the discussion was closed the second column was 0; when open it expanded to clamp(320px, 24vw, 440px). This caused the entire page content to reflow inside the grid.

The new approach defines two CSS custom properties on .branch-page:

  • --discussion-sidebar-width: clamp(480px, 30vw, 620px) — wider than before (was 320–440px, now 480–620px)
  • --discussion-sidebar-gap: 1.5rem

When .discussion-open is toggled on .branch-page, the main content constrains itself with max-width: calc(2200px - var(--discussion-sidebar-width) - var(--discussion-sidebar-gap)) and adds a small padding-right. Both properties transition smoothly at 180ms. The .branch-review-content wrapper class is removed entirely since it is no longer needed.

Convert discussion sidebar to a fixed-position overlay panel

Intent: Position the discussion sidebar as a viewport-fixed panel on the right edge rather than an inline grid cell, giving it a drop shadow and full-height stretch. Use CSS sibling selectors instead of descendant selectors for open-state styling.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -227,8 +222,8 @@
-        min-height: min(76vh, 880px);
-        height: min(76vh, 880px);
+        min-height: 100%;
+        height: 100%;
@@ -238,21 +233,30 @@
       .discussion-sidebar-shell {
+        position: fixed;
+        top: 1.25rem;
+        right: 1.25rem;
+        bottom: 1.25rem;
+        width: var(--discussion-sidebar-width);
+        max-width: calc(100vw - 2rem);
         min-width: 0;
         opacity: 0;
         pointer-events: none;
-        transform: translateX(1rem);
-        overflow: hidden;
+        transform: translateX(calc(100% + var(--discussion-sidebar-gap)));
+        z-index: 40;
         transition: opacity 160ms ease, transform 180ms ease;
       }
-      .branch-review-shell.discussion-open .discussion-sidebar-shell {
+      .branch-page.discussion-open + .discussion-backdrop + .discussion-sidebar-shell {
         opacity: 1;
         pointer-events: auto;
         transform: translateX(0);
       }
@@ -255,8 +255,12 @@
       .discussion-sidebar-shell .discussion-panel {
-        position: sticky;
-        top: 4.4rem;
+        position: relative;
+        top: 0;
+        min-height: 0;
+        height: 100%;
+        box-shadow: 0 22px 46px rgba(4, 9, 18, 0.22);

The .discussion-sidebar-shell is now position: fixed with insets of 1.25rem on top, right, and bottom. It uses width: var(--discussion-sidebar-width) and caps at calc(100vw - 2rem) on small screens.

The hidden-state transform changed from translateX(1rem) (a subtle nudge) to translateX(calc(100% + var(--discussion-sidebar-gap))), which fully slides the panel off-screen to the right.

Because the sidebar is no longer a descendant of the shell div, the open-state selector changed from a descendant selector (.branch-review-shell.discussion-open .discussion-sidebar-shell) to a CSS sibling combinator (.branch-page.discussion-open + .discussion-backdrop + .discussion-sidebar-shell). This relies on the new DOM order where <main>, <div.discussion-backdrop>, and <aside.discussion-sidebar-shell> are adjacent siblings.

The inner .discussion-panel switches from position: sticky (for scroll-pinning inside the grid) to position: relative; height: 100% with a prominent box-shadow, since the panel now fills its fixed-position container. The panel height also changes from a capped min(76vh, 880px) to a full 100%.

Flatten the HTML structure: remove wrapper div, hoist sidebar and backdrop

Intent: Remove the `#branch-review-shell` and `.branch-review-content` wrapper divs so that the review layout, diff section, discussion backdrop, and discussion sidebar are all direct children or siblings of `<main>`, enabling the new CSS sibling selectors.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -453,7 +453,7 @@
 {% block body %}
-    <main class="branch-page">
+    <main id="branch-page" class="branch-page">
@@ -494,146 +498,142 @@
-      <div id="branch-review-shell" class="branch-review-shell">
-        <div class="branch-review-content">
-          <div class="review-layout">
+      <div class="review-layout">
@@ -633,10 +633,10 @@
-        <div id="discussion-backdrop" class="discussion-backdrop" hidden></div>
-        <aside id="discussion-sidebar" class="discussion-sidebar-shell" aria-hidden="true">
-      </div>
-    </main>
+    </main>
+
+    <div id="discussion-backdrop" class="discussion-backdrop" hidden></div>
+    <aside id="discussion-sidebar" class="discussion-sidebar-shell" aria-hidden="true">

The template previously had this nesting:

<main>
  <div#branch-review-shell>
    <div.branch-review-content>
      <div.review-layout> ... </div>
      <section.diff-panel> ... </section>
    </div>
    <div#discussion-backdrop />
    <aside#discussion-sidebar />
  </div>
</main>

After this change:

<main#branch-page>
  <div.review-layout> ... </div>
  <section.diff-panel> ... </section>
</main>
<div#discussion-backdrop />
<aside#discussion-sidebar />

The two wrapper divs (#branch-review-shell and .branch-review-content) are removed. The <main> element gains id="branch-page" so JavaScript can reference it directly. The backdrop and sidebar are hoisted outside <main> entirely, making them siblings — which is required for the + sibling CSS selectors to work.

Update mobile responsive rules for the new DOM structure

Intent: Adjust the mobile media query breakpoint styles to reference the new class names and sibling selectors instead of the removed `.branch-review-shell`.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -413,21 +417,21 @@
-        .branch-review-shell,
-        .branch-review-shell.discussion-open {
-          display: block;
+        .branch-page.discussion-open {
+          max-width: none;
+          padding-right: 2rem;
         }
         .discussion-sidebar-shell {
           position: fixed;
           top: 0;
           right: 0;
           bottom: 0;
-          width: min(92vw, 26rem);
+          width: min(94vw, 32rem);
           padding: 1rem 0.75rem 1rem 0;
           z-index: 40;
           transform: translateX(100%);
         }
-        .branch-review-shell.discussion-open .discussion-sidebar-shell {
+        .branch-page.discussion-open + .discussion-backdrop + .discussion-sidebar-shell {
           transform: translateX(0);
         }
@@ -443,7 +447,7 @@
-        .branch-review-shell.discussion-open .discussion-backdrop {
+        .branch-page.discussion-open + .discussion-backdrop {
           display: block;
         }

On mobile, the old rules set .branch-review-shell and its .discussion-open variant to display: block to collapse the grid. The new rules instead set .branch-page.discussion-open to max-width: none; padding-right: 2rem — removing the max-width constraint so content fills the screen.

The mobile sidebar width increases from min(92vw, 26rem) to min(94vw, 32rem), giving users a slightly wider drawer on small screens.

All mobile selectors for the sidebar and backdrop are updated to use the sibling combinator pattern (.branch-page.discussion-open + .discussion-backdrop + .discussion-sidebar-shell).

Update JavaScript to reference the new `branch-page` element ID

Intent: Replace all JS references from `branch-review-shell` to `branch-page` so the class toggle targets the `<main>` element.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -720,7 +720,7 @@
-        const reviewShell = document.getElementById('branch-review-shell');
+        const branchPage = document.getElementById('branch-page');
@@ -762,8 +762,8 @@
         function setDiscussionOpen(nextOpen, options = {}) {
           discussionOpen = !!nextOpen;
-          if (reviewShell) {
-            reviewShell.classList.toggle('discussion-open', discussionOpen);
+          if (branchPage) {
+            branchPage.classList.toggle('discussion-open', discussionOpen);
           }

The JavaScript variable reviewShell (which pointed to #branch-review-shell) is renamed to branchPage and now references document.getElementById('branch-page'). The setDiscussionOpen function toggles the discussion-open class on this element, which triggers all the CSS transitions on <main> and, via sibling selectors, on the backdrop and sidebar.

Guard keyboard shortcuts against firing inside text inputs

Intent: Prevent the `[` and `]` review-navigation keyboard shortcuts from triggering when the user is typing in an input field, textarea, or contentEditable element — especially relevant now that the discussion input is always in the DOM as a fixed panel.

Affected files: crates/pika-news/templates/detail.html

Evidence
@@ -1155,6 +1155,15 @@
           window.addEventListener('keydown', (event) => {
+            const target = event.target;
+            const typingTarget = target instanceof HTMLElement && (
+              target.tagName === 'INPUT' ||
+              target.tagName === 'TEXTAREA' ||
+              target.isContentEditable
+            );
+            if (typingTarget) {
+              return;
+            }
             if (event.key === '[' && reviewPrevBtn && reviewPrevBtn.dataset.targetId) {
               navigateReview(reviewPrevBtn.dataset.targetId);
             }

A new guard is added at the top of the global keydown listener. It checks whether event.target is an INPUT, TEXTAREA, or contentEditable element. If so, the handler returns early without processing any shortcut keys.

This is particularly important because the discussion sidebar's text input is now always present in the DOM as a fixed-position element (rather than being hidden inside a collapsed grid column). Without this guard, typing [ or ] in the chat input would trigger review navigation.

Update Rust integration test for renamed element ID

Intent: Keep the server-side rendering test in sync with the template change from `#branch-review-shell` to `#branch-page`.

Affected files: crates/pika-news/src/web.rs

Evidence
@@ -8312,7 +8312,7 @@
-        assert!(rendered.contains("id=\"branch-review-shell\""));
+        assert!(rendered.contains("id=\"branch-page\""));

The integration test at web.rs:8315 previously asserted that the rendered HTML contained id="branch-review-shell". Since that element was removed and the <main> tag now carries id="branch-page", the assertion is updated accordingly. This is the only back-end change in the branch — the rest is purely front-end template and CSS work.

Diff