فهرست منبع

fix(app): unmount hidden session panes (#35628)

Luke Parker 1 ماه پیش
والد
کامیت
d8a57ee15c

+ 3 - 4
packages/app/e2e/regression/review-terminal-stacked.spec.ts

@@ -168,8 +168,8 @@ test("keeps the review tree and terminal sized when both panels are open", async
   await expectTree(page, 2_773, "action.yml")
 
   await page.getByRole("button", { name: "Toggle file tree" }).click()
-  await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveAttribute("aria-hidden", "true")
-  await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1)
+  await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveCount(0)
+  await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(0)
   await page.getByRole("button", { name: "Toggle file tree" }).click()
   await expectTree(page, 2_773, "action.yml")
 
@@ -181,8 +181,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
   await expectTree(page, 2_773, "action.yml")
 
   await page.getByRole("button", { name: "Toggle review" }).click()
-  await expect(page.locator("#review-panel")).toHaveAttribute("aria-hidden", "true")
-  await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1)
+  await expect(page.locator("#review-panel")).toHaveCount(0)
   await page.getByRole("button", { name: "Toggle review" }).click()
   await expectTree(page, 2_773, "action.yml")
   await page.setViewportSize({ width: 1_000, height: 700 })

+ 3 - 8
packages/app/src/pages/session.tsx

@@ -2198,7 +2198,7 @@ export default function Page() {
           </Show>
         </div>
 
-        <Show when={!newSessionDesign()}>
+        <Show when={!newSessionDesign() && desktopSidePanelOpen()}>
           <SessionSidePanel
             canReview={canReview}
             diffs={reviewDiffs}
@@ -2217,13 +2217,8 @@ export default function Page() {
         <Show when={newSessionDesign()}>
           <Show when={isDesktop() ? desktopV2PanelLayout().visible : terminalOpen()}>
             <div class="min-w-0 h-full flex flex-1 flex-col">
-              <Show when={isDesktop()}>
-                <div
-                  classList={{
-                    "min-h-0 flex-1": desktopV2ReviewOpen() || desktopFileTreeOpen(),
-                    "size-0 shrink-0 overflow-hidden": !(desktopV2ReviewOpen() || desktopFileTreeOpen()),
-                  }}
-                >
+              <Show when={isDesktop() && (desktopV2ReviewOpen() || desktopFileTreeOpen())}>
+                <div class="min-h-0 flex-1">
                   <SessionSidePanel
                     canReview={canReview}
                     diffs={reviewDiffs}

+ 225 - 239
packages/app/src/pages/session/session-side-panel.tsx

@@ -81,7 +81,6 @@ export function SessionSidePanel(props: {
       }),
   )
   const open = createMemo(() => reviewOpen() || fileOpen())
-  const rendered = createMemo<boolean>((previous) => previous || open(), false)
   const reviewTab = createMemo(() => isDesktop())
   const panelWidth = createMemo(() => {
     if (!open()) return "0px"
@@ -164,10 +163,6 @@ export function SessionSidePanel(props: {
   const openedTabs = tabState.openedTabs
   const activeTab = tabState.activeTab
   const activeFileTab = tabState.activeFileTab
-  const reviewContentRendered = createMemo<boolean>(
-    (previous) => previous || (reviewOpen() && activeTab() === "review"),
-    false,
-  )
 
   const fileTreeTab = () => layout.fileTree.tab()
 
@@ -272,233 +267,220 @@ export function SessionSidePanel(props: {
         }}
         style={{ width: panelWidth() }}
       >
-        <Show when={rendered()}>
+        <Show when={open()}>
           <div
             class="size-full flex"
             classList={{
               "border-l border-border-weaker-base": !settings.general.newLayoutDesigns(),
             }}
           >
-            <div
-              aria-hidden={!reviewOpen()}
-              inert={!reviewOpen()}
-              class="relative min-w-0 h-full flex-1 overflow-hidden bg-background-base"
-              classList={{
-                "pointer-events-none": !reviewOpen(),
-              }}
-            >
-              <div class="size-full min-w-0 h-full bg-background-base">
-                <DragDropProvider
-                  onDragStart={handleDragStart}
-                  onDragEnd={handleDragEnd}
-                  onDragOver={handleDragOver}
-                  collisionDetector={closestCenter}
-                >
-                  <DragDropSensors />
-                  <ConstrainDragYAxis />
-                  <Tabs value={activeTab()} onChange={activateTab}>
-                    <div class="sticky top-0 shrink-0 flex">
-                      <Tabs.List
-                        ref={(el: HTMLDivElement) => {
-                          const stop = createFileTabListSync({ el, contextOpen })
-                          onCleanup(stop)
-                        }}
-                      >
-                        <Show when={reviewTab() && props.canReview()}>
-                          <Tabs.Trigger
-                            value="review"
-                            id={reviewTabID}
-                            aria-controls={activeTab() === "review" ? reviewTabPanelID : undefined}
-                          >
-                            <div class="flex items-center gap-1.5">
-                              <div>{language.t("session.tab.review")}</div>
-                              <Show when={props.hasReview()}>
-                                <div>{props.reviewCount()}</div>
-                              </Show>
-                            </div>
-                          </Tabs.Trigger>
-                        </Show>
-                        <Show when={contextOpen()}>
-                          <Tabs.Trigger
-                            value="context"
-                            closeButton={
-                              <TooltipKeybind
-                                title={language.t("common.closeTab")}
-                                keybind={command.keybind("tab.close")}
-                                placement="bottom"
-                                gutter={10}
-                              >
-                                <IconButton
-                                  icon="close-small"
-                                  variant="ghost"
-                                  class="h-5 w-5"
-                                  onClick={() => tabs().close("context")}
-                                  aria-label={language.t("common.closeTab")}
-                                />
-                              </TooltipKeybind>
-                            }
-                            hideCloseButton
-                            onMiddleClick={() => tabs().close("context")}
-                          >
-                            <div class="flex items-center gap-2">
-                              <SessionContextUsage variant="indicator" />
-                              <div>{language.t("session.tab.context")}</div>
-                            </div>
-                          </Tabs.Trigger>
-                        </Show>
-                        <SortableProvider ids={openedTabs()}>
-                          <For each={panelTabs()}>
-                            {(tab) => (
-                              <Show
-                                when={tab === SESSION_OPEN_FILE_TAB}
-                                fallback={
-                                  <SortableTab
-                                    tab={tab}
-                                    temporary={temporaryTab() === tab}
-                                    onTabClose={tabs().close}
-                                    onTabDoubleClick={temporaryTab() === tab ? openTab : undefined}
+            <Show when={reviewOpen()}>
+              <div class="relative min-w-0 h-full flex-1 overflow-hidden bg-background-base">
+                <div class="size-full min-w-0 h-full bg-background-base">
+                  <DragDropProvider
+                    onDragStart={handleDragStart}
+                    onDragEnd={handleDragEnd}
+                    onDragOver={handleDragOver}
+                    collisionDetector={closestCenter}
+                  >
+                    <DragDropSensors />
+                    <ConstrainDragYAxis />
+                    <Tabs value={activeTab()} onChange={activateTab}>
+                      <div class="sticky top-0 shrink-0 flex">
+                        <Tabs.List
+                          ref={(el: HTMLDivElement) => {
+                            const stop = createFileTabListSync({ el, contextOpen })
+                            onCleanup(stop)
+                          }}
+                        >
+                          <Show when={reviewTab() && props.canReview()}>
+                            <Tabs.Trigger
+                              value="review"
+                              id={reviewTabID}
+                              aria-controls={activeTab() === "review" ? reviewTabPanelID : undefined}
+                            >
+                              <div class="flex items-center gap-1.5">
+                                <div>{language.t("session.tab.review")}</div>
+                                <Show when={props.hasReview()}>
+                                  <div>{props.reviewCount()}</div>
+                                </Show>
+                              </div>
+                            </Tabs.Trigger>
+                          </Show>
+                          <Show when={contextOpen()}>
+                            <Tabs.Trigger
+                              value="context"
+                              closeButton={
+                                <TooltipKeybind
+                                  title={language.t("common.closeTab")}
+                                  keybind={command.keybind("tab.close")}
+                                  placement="bottom"
+                                  gutter={10}
+                                >
+                                  <IconButton
+                                    icon="close-small"
+                                    variant="ghost"
+                                    class="h-5 w-5"
+                                    onClick={() => tabs().close("context")}
+                                    aria-label={language.t("common.closeTab")}
                                   />
-                                }
-                              >
-                                <Tabs.Trigger
-                                  value={SESSION_OPEN_FILE_TAB}
-                                  closeButton={
-                                    <TooltipKeybind
-                                      title={language.t("common.closeTab")}
-                                      keybind={command.keybind("tab.close")}
-                                      placement="bottom"
-                                      gutter={10}
-                                    >
-                                      <IconButton
-                                        icon="close-small"
-                                        variant="ghost"
-                                        class="h-5 w-5"
-                                        onClick={() => tabs().close(SESSION_OPEN_FILE_TAB)}
-                                        aria-label={language.t("common.closeTab")}
-                                      />
-                                    </TooltipKeybind>
+                                </TooltipKeybind>
+                              }
+                              hideCloseButton
+                              onMiddleClick={() => tabs().close("context")}
+                            >
+                              <div class="flex items-center gap-2">
+                                <SessionContextUsage variant="indicator" />
+                                <div>{language.t("session.tab.context")}</div>
+                              </div>
+                            </Tabs.Trigger>
+                          </Show>
+                          <SortableProvider ids={openedTabs()}>
+                            <For each={panelTabs()}>
+                              {(tab) => (
+                                <Show
+                                  when={tab === SESSION_OPEN_FILE_TAB}
+                                  fallback={
+                                    <SortableTab
+                                      tab={tab}
+                                      temporary={temporaryTab() === tab}
+                                      onTabClose={tabs().close}
+                                      onTabDoubleClick={temporaryTab() === tab ? openTab : undefined}
+                                    />
                                   }
-                                  hideCloseButton
-                                  onMiddleClick={() => tabs().close(SESSION_OPEN_FILE_TAB)}
                                 >
-                                  <div class="flex items-center gap-1.5 italic">
-                                    <Icon name="open-file" size="small" />
-                                    <span>{language.t("command.file.open")}</span>
-                                  </div>
-                                </Tabs.Trigger>
-                              </Show>
-                            )}
-                          </For>
-                        </SortableProvider>
-                        <div class="bg-background-stronger h-full shrink-0 sticky right-0 z-10 flex items-center justify-center pr-3">
-                          <TooltipKeybind
-                            title={language.t("command.file.open")}
-                            keybind={command.keybind("file.open")}
-                            class="flex items-center"
-                          >
-                            <IconButton
-                              icon="plus-small"
-                              variant="ghost"
-                              iconSize="large"
-                              class="!rounded-md"
-                              onClick={() => {
-                                if (props.fileBrowserState) {
-                                  openFileBrowser()
-                                  return
-                                }
-                                void import("@/components/dialog-select-file").then((x) => {
-                                  dialog.show(() => <x.DialogSelectFile mode="files" onOpenFile={showAllFiles} />)
-                                })
-                              }}
-                              aria-label={language.t("command.file.open")}
-                            />
-                          </TooltipKeybind>
-                        </div>
-                      </Tabs.List>
-                    </div>
-
-                    <Show when={reviewTab() && props.canReview() && reviewContentRendered()}>
-                      <div
-                        id={reviewTabPanelID}
-                        role="tabpanel"
-                        aria-labelledby={reviewTabID}
-                        aria-hidden={activeTab() !== "review"}
-                        inert={activeTab() !== "review"}
-                        tabIndex={props.reviewHasFocusableContent() ? undefined : 0}
-                        data-slot="tabs-content"
-                        class="flex flex-col h-full overflow-hidden contain-strict"
-                        classList={{ hidden: activeTab() !== "review" }}
-                      >
-                        {props.reviewPanel()}
+                                  <Tabs.Trigger
+                                    value={SESSION_OPEN_FILE_TAB}
+                                    closeButton={
+                                      <TooltipKeybind
+                                        title={language.t("common.closeTab")}
+                                        keybind={command.keybind("tab.close")}
+                                        placement="bottom"
+                                        gutter={10}
+                                      >
+                                        <IconButton
+                                          icon="close-small"
+                                          variant="ghost"
+                                          class="h-5 w-5"
+                                          onClick={() => tabs().close(SESSION_OPEN_FILE_TAB)}
+                                          aria-label={language.t("common.closeTab")}
+                                        />
+                                      </TooltipKeybind>
+                                    }
+                                    hideCloseButton
+                                    onMiddleClick={() => tabs().close(SESSION_OPEN_FILE_TAB)}
+                                  >
+                                    <div class="flex items-center gap-1.5 italic">
+                                      <Icon name="open-file" size="small" />
+                                      <span>{language.t("command.file.open")}</span>
+                                    </div>
+                                  </Tabs.Trigger>
+                                </Show>
+                              )}
+                            </For>
+                          </SortableProvider>
+                          <div class="bg-background-stronger h-full shrink-0 sticky right-0 z-10 flex items-center justify-center pr-3">
+                            <TooltipKeybind
+                              title={language.t("command.file.open")}
+                              keybind={command.keybind("file.open")}
+                              class="flex items-center"
+                            >
+                              <IconButton
+                                icon="plus-small"
+                                variant="ghost"
+                                iconSize="large"
+                                class="!rounded-md"
+                                onClick={() => {
+                                  if (props.fileBrowserState) {
+                                    openFileBrowser()
+                                    return
+                                  }
+                                  void import("@/components/dialog-select-file").then((x) => {
+                                    dialog.show(() => <x.DialogSelectFile mode="files" onOpenFile={showAllFiles} />)
+                                  })
+                                }}
+                                aria-label={language.t("command.file.open")}
+                              />
+                            </TooltipKeybind>
+                          </div>
+                        </Tabs.List>
                       </div>
-                    </Show>
 
-                    <Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
+                      <Show when={reviewTab() && props.canReview() && activeTab() === "review"}>
+                        <div
+                          id={reviewTabPanelID}
+                          role="tabpanel"
+                          aria-labelledby={reviewTabID}
+                          tabIndex={props.reviewHasFocusableContent() ? undefined : 0}
+                          data-slot="tabs-content"
+                          class="flex flex-col h-full overflow-hidden contain-strict"
+                        >
+                          {props.reviewPanel()}
+                        </div>
+                      </Show>
+
                       <Show when={activeTab() === "empty"}>
-                        <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
-                          <div class="h-full px-6 pb-42 -mt-4 flex flex-col items-center justify-center text-center gap-6">
-                            <Mark class="w-14 opacity-10" />
-                            <div class="text-14-regular text-text-weak max-w-56">
-                              {language.t("session.files.selectToOpen")}
+                        <Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
+                          <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
+                            <div class="h-full px-6 pb-42 -mt-4 flex flex-col items-center justify-center text-center gap-6">
+                              <Mark class="w-14 opacity-10" />
+                              <div class="text-14-regular text-text-weak max-w-56">
+                                {language.t("session.files.selectToOpen")}
+                              </div>
                             </div>
                           </div>
-                        </div>
+                        </Tabs.Content>
                       </Show>
-                    </Tabs.Content>
 
-                    <Show when={contextOpen()}>
-                      <Tabs.Content value="context" class="flex flex-col h-full overflow-hidden contain-strict">
-                        <Show when={activeTab() === "context"}>
+                      <Show when={activeTab() === "context"}>
+                        <Tabs.Content value="context" class="flex flex-col h-full overflow-hidden contain-strict">
                           <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
                             <SessionContextTab />
                           </div>
-                        </Show>
-                      </Tabs.Content>
-                    </Show>
+                        </Tabs.Content>
+                      </Show>
 
-                    <Show when={browserTab()}>
-                      <SessionFileBrowserTab
-                        tab={browserTab()!}
-                        placeholder={browserTab() === SESSION_OPEN_FILE_TAB}
-                        active={file.pathFromTab(browserTab()!)}
-                        kinds={browserKinds()}
-                        state={props.fileBrowserState!}
-                        onSelect={(path) => previewTab(file.tab(path))}
-                        onSelectPermanent={(path) => openTab(file.tab(path))}
-                        filterRef={(element) => (fileFilter = element)}
-                      />
-                    </Show>
+                      <Show when={browserTab()}>
+                        <SessionFileBrowserTab
+                          tab={browserTab()!}
+                          placeholder={browserTab() === SESSION_OPEN_FILE_TAB}
+                          active={file.pathFromTab(browserTab()!)}
+                          kinds={browserKinds()}
+                          state={props.fileBrowserState!}
+                          onSelect={(path) => previewTab(file.tab(path))}
+                          onSelectPermanent={(path) => openTab(file.tab(path))}
+                          filterRef={(element) => (fileFilter = element)}
+                        />
+                      </Show>
 
-                    <Show when={!props.fileBrowserState && activeFileTab()} keyed>
-                      {(tab) => <FileTabContent tab={tab} />}
-                    </Show>
-                  </Tabs>
-                  <DragOverlay>
-                    <Show when={store.activeDraggable} keyed>
-                      {(tab) => {
-                        const path = file.pathFromTab(tab)
-                        return (
-                          <div data-component="tabs-drag-preview">
-                            <Show when={path}>
-                              {(p) => <FileVisual active path={p()} temporary={temporaryTab() === tab} />}
-                            </Show>
-                          </div>
-                        )
-                      }}
-                    </Show>
-                  </DragOverlay>
-                </DragDropProvider>
+                      <Show when={!props.fileBrowserState && activeFileTab()} keyed>
+                        {(tab) => <FileTabContent tab={tab} />}
+                      </Show>
+                    </Tabs>
+                    <DragOverlay>
+                      <Show when={store.activeDraggable} keyed>
+                        {(tab) => {
+                          const path = file.pathFromTab(tab)
+                          return (
+                            <div data-component="tabs-drag-preview">
+                              <Show when={path}>
+                                {(p) => <FileVisual active path={p()} temporary={temporaryTab() === tab} />}
+                              </Show>
+                            </div>
+                          )
+                        }}
+                      </Show>
+                    </DragOverlay>
+                  </DragDropProvider>
+                </div>
               </div>
-            </div>
+            </Show>
 
-            <Show when={shown()}>
+            <Show when={fileOpen()}>
               <div
                 id="file-tree-panel"
-                aria-hidden={!fileOpen()}
-                inert={!fileOpen()}
                 class="relative min-w-0 h-full shrink-0 overflow-hidden"
                 classList={{
-                  "pointer-events-none": !fileOpen(),
                   "transition-[width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
                     !props.size.active(),
                 }}
@@ -526,45 +508,49 @@ export function SessionSidePanel(props: {
                         {language.t("session.files.all")}
                       </Tabs.Trigger>
                     </Tabs.List>
-                    <Tabs.Content value="changes" class="bg-background-stronger px-3 py-0">
-                      <Switch>
-                        <Match when={props.hasReview() || !props.diffsReady()}>
-                          <Show
-                            when={props.diffsReady()}
-                            fallback={
-                              <div class="px-2 py-2 text-12-regular text-text-weak">
-                                {language.t("common.loading")}
-                                {language.t("common.loading.ellipsis")}
-                              </div>
-                            }
-                          >
+                    <Show when={fileTreeTab() === "changes"}>
+                      <Tabs.Content value="changes" class="bg-background-stronger px-3 py-0">
+                        <Switch>
+                          <Match when={props.hasReview() || !props.diffsReady()}>
+                            <Show
+                              when={props.diffsReady()}
+                              fallback={
+                                <div class="px-2 py-2 text-12-regular text-text-weak">
+                                  {language.t("common.loading")}
+                                  {language.t("common.loading.ellipsis")}
+                                </div>
+                              }
+                            >
+                              <FileTree
+                                path=""
+                                class="pt-3"
+                                allowed={diffFiles()}
+                                kinds={kinds()}
+                                draggable={false}
+                                active={props.activeDiff}
+                                onFileClick={(node) => props.focusReviewDiff(node.path)}
+                              />
+                            </Show>
+                          </Match>
+                        </Switch>
+                      </Tabs.Content>
+                    </Show>
+                    <Show when={fileTreeTab() === "all"}>
+                      <Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
+                        <Switch>
+                          <Match when={nofiles()}>{empty(language.t("session.files.empty"))}</Match>
+                          <Match when={true}>
                             <FileTree
                               path=""
                               class="pt-3"
-                              allowed={diffFiles()}
+                              modified={diffFiles()}
                               kinds={kinds()}
-                              draggable={false}
-                              active={props.activeDiff}
-                              onFileClick={(node) => props.focusReviewDiff(node.path)}
+                              onFileClick={(node) => openTab(file.tab(node.path))}
                             />
-                          </Show>
-                        </Match>
-                      </Switch>
-                    </Tabs.Content>
-                    <Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
-                      <Switch>
-                        <Match when={nofiles()}>{empty(language.t("session.files.empty"))}</Match>
-                        <Match when={true}>
-                          <FileTree
-                            path=""
-                            class="pt-3"
-                            modified={diffFiles()}
-                            kinds={kinds()}
-                            onFileClick={(node) => openTab(file.tab(node.path))}
-                          />
-                        </Match>
-                      </Switch>
-                    </Tabs.Content>
+                          </Match>
+                        </Switch>
+                      </Tabs.Content>
+                    </Show>
                   </Tabs>
                 </div>
                 <Show when={fileOpen()}>

+ 55 - 55
packages/session-ui/src/v2/components/session-review-v2.tsx

@@ -74,62 +74,62 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
 
   return (
     <div data-component="session-review-v2-sidebar-root">
-      <aside
-        data-slot="session-review-v2-sidebar"
-        data-resizing={resizing() ? "" : undefined}
-        aria-hidden={!props.open}
-        inert={!props.open}
-        style={{ width: props.open ? `${width()}px` : "0px" }}
-      >
-        <div data-slot="session-review-v2-sidebar-header">
-          <div data-slot="session-review-v2-sidebar-title">{props.title}</div>
-          {props.stats}
-        </div>
-        <div data-slot="session-review-v2-sidebar-filter">
-          <TextInputV2
-            type="search"
-            value={props.filter}
-            onInput={(event) => props.onFilterChange(event.currentTarget.value)}
-            onKeyDown={props.onFilterKeyDown}
-            autofocus={props.filterAutofocus}
-            ref={props.filterRef}
-            role={props.filterControls ? "combobox" : undefined}
-            aria-autocomplete={props.filterControls ? "list" : undefined}
-            aria-controls={props.filterControls}
-            aria-activedescendant={props.filterActiveDescendant}
-            aria-expanded={props.filterControls ? props.filterExpanded : undefined}
-            showClearButton={props.filter.length > 0}
-            clearLabel={i18n.t("ui.list.clearFilter")}
-            onClearClick={() => props.onFilterChange("")}
-            placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
-            aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
-            leadingIcon={
-              <svg
-                width="14"
-                height="14"
-                viewBox="0 0 14 14"
-                fill="none"
-                xmlns="http://www.w3.org/2000/svg"
-                aria-hidden="true"
-              >
-                <path
-                  d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
-                  stroke="currentColor"
-                  stroke-linecap="square"
-                />
-              </svg>
-            }
-          />
-        </div>
-        <ScrollView
-          data-slot="session-review-v2-sidebar-tree"
-          class="group/file-tree-v2"
-          thumbVisibility="scroll"
-          viewportRef={props.viewportRef}
+      <Show when={props.open}>
+        <aside
+          data-slot="session-review-v2-sidebar"
+          data-resizing={resizing() ? "" : undefined}
+          style={{ width: `${width()}px` }}
         >
-          {props.children}
-        </ScrollView>
-      </aside>
+          <div data-slot="session-review-v2-sidebar-header">
+            <div data-slot="session-review-v2-sidebar-title">{props.title}</div>
+            {props.stats}
+          </div>
+          <div data-slot="session-review-v2-sidebar-filter">
+            <TextInputV2
+              type="search"
+              value={props.filter}
+              onInput={(event) => props.onFilterChange(event.currentTarget.value)}
+              onKeyDown={props.onFilterKeyDown}
+              autofocus={props.filterAutofocus}
+              ref={props.filterRef}
+              role={props.filterControls ? "combobox" : undefined}
+              aria-autocomplete={props.filterControls ? "list" : undefined}
+              aria-controls={props.filterControls}
+              aria-activedescendant={props.filterActiveDescendant}
+              aria-expanded={props.filterControls ? props.filterExpanded : undefined}
+              showClearButton={props.filter.length > 0}
+              clearLabel={i18n.t("ui.list.clearFilter")}
+              onClearClick={() => props.onFilterChange("")}
+              placeholder={i18n.t("ui.sessionReviewV2.filterFiles")}
+              aria-label={i18n.t("ui.sessionReviewV2.filterFiles")}
+              leadingIcon={
+                <svg
+                  width="14"
+                  height="14"
+                  viewBox="0 0 14 14"
+                  fill="none"
+                  xmlns="http://www.w3.org/2000/svg"
+                  aria-hidden="true"
+                >
+                  <path
+                    d="M12.25 12.25L10.0625 10.0625M11.0833 6.41667C11.0833 8.994 8.994 11.0833 6.41667 11.0833C3.83934 11.0833 1.75 8.994 1.75 6.41667C1.75 3.83934 3.83934 1.75 6.41667 1.75C8.994 1.75 11.0833 3.83934 11.0833 6.41667Z"
+                    stroke="currentColor"
+                    stroke-linecap="square"
+                  />
+                </svg>
+              }
+            />
+          </div>
+          <ScrollView
+            data-slot="session-review-v2-sidebar-tree"
+            class="group/file-tree-v2"
+            thumbVisibility="scroll"
+            viewportRef={props.viewportRef}
+          >
+            {props.children}
+          </ScrollView>
+        </aside>
+      </Show>
       <Show when={props.open && props.onWidthChange}>
         <div data-slot="session-review-v2-sidebar-resize" onPointerDown={() => setResizing(true)}>
           <ResizeHandle