From 633a3ba03adf9983e361efe994f34d405b573cbc Mon Sep 17 00:00:00 2001 From: David Hill Date: Mon, 2 Mar 2026 16:05:16 +0000 Subject: [PATCH] ui: avoid session review header clipping Move the session review header outside the scroll viewport and drop strict containment so shadows can render without being cropped. --- packages/ui/src/components/session-review.css | 13 +- packages/ui/src/components/session-review.tsx | 577 +++++++++--------- 2 files changed, 295 insertions(+), 295 deletions(-) diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index dfbb171d8..014a70e74 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -3,11 +3,10 @@ flex-direction: column; gap: 0px; height: 100%; - overflow-y: auto; - scrollbar-width: none; - contain: strict; - &::-webkit-scrollbar { - display: none; + + [data-slot="session-review-scroll"] { + flex: 1 1 auto; + min-height: 0; } .scroll-view__viewport { @@ -21,8 +20,6 @@ } [data-slot="session-review-header"] { - position: sticky; - top: 0; z-index: 120; background-color: var(--background-stronger); height: 40px; @@ -63,7 +60,7 @@ } [data-component="sticky-accordion-header"] { - --sticky-accordion-top: 40px; + --sticky-accordion-top: 0px; } [data-slot="session-review-accordion-item"][data-selected] diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 77bd9506d..2f74207d5 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -554,20 +554,7 @@ export const SessionReview = (props: SessionReviewProps) => { } return ( - { - scroll = el - props.scrollRef?.(el) - }} - onScroll={props.onScroll as any} - onKeyDown={handleReviewKeyDown} - classList={{ - ...(props.classList ?? {}), - [props.classes?.root ?? ""]: !!props.classes?.root, - [props.class ?? ""]: !!props.class, - }} - > +
{props.title ?? i18n.t("ui.sessionReview.title")}
@@ -599,301 +586,317 @@ export const SessionReview = (props: SessionReviewProps) => { {props.actions}
- - (searchHits().length ? Math.min(searchActive(), searchHits().length - 1) : 0)} - count={() => searchHits().length} - setInput={(el) => { - searchInput = el - }} - onInput={(value) => { - setSearchQuery(value) - setSearchActive(0) - }} - onKeyDown={(event) => handleSearchInputKeyDown(event)} - onClose={closeSearch} - onPrev={() => navigateSearch(-1)} - onNext={() => navigateSearch(1)} - /> - -
- - - - {(file) => { - let wrapper: HTMLDivElement | undefined - const diff = createMemo(() => diffs().get(file)) - const item = () => diff()! + { + scroll = el + props.scrollRef?.(el) + }} + onScroll={props.onScroll as any} + onKeyDown={handleReviewKeyDown} + classList={{ + [props.classes?.root ?? ""]: !!props.classes?.root, + }} + > + + (searchHits().length ? Math.min(searchActive(), searchHits().length - 1) : 0)} + count={() => searchHits().length} + setInput={(el) => { + searchInput = el + }} + onInput={(value) => { + setSearchQuery(value) + setSearchActive(0) + }} + onKeyDown={(event) => handleSearchInputKeyDown(event)} + onClose={closeSearch} + onPrev={() => navigateSearch(-1)} + onNext={() => navigateSearch(1)} + /> + - const expanded = createMemo(() => open().includes(file)) - const force = () => !!store.force[file] +
+ + + + {(file) => { + let wrapper: HTMLDivElement | undefined - const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) - const commentedLines = createMemo(() => comments().map((c) => c.selection)) + const diff = createMemo(() => diffs().get(file)) + const item = () => diff()! - const beforeText = () => (typeof item().before === "string" ? item().before : "") - const afterText = () => (typeof item().after === "string" ? item().after : "") - const changedLines = () => item().additions + item().deletions - const mediaKind = createMemo(() => mediaKindFromPath(file)) + const expanded = createMemo(() => open().includes(file)) + const force = () => !!store.force[file] - const tooLarge = createMemo(() => { - if (!expanded()) return false - if (force()) return false - if (mediaKind()) return false - return changedLines() > MAX_DIFF_CHANGED_LINES - }) + const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) + const commentedLines = createMemo(() => comments().map((c) => c.selection)) - const isAdded = () => item().status === "added" || (beforeText().length === 0 && afterText().length > 0) - const isDeleted = () => - item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) + const beforeText = () => (typeof item().before === "string" ? item().before : "") + const afterText = () => (typeof item().after === "string" ? item().after : "") + const changedLines = () => item().additions + item().deletions + const mediaKind = createMemo(() => mediaKindFromPath(file)) - const selectedLines = createMemo(() => { - const current = selection() - if (!current || current.file !== file) return null - return current.range - }) + const tooLarge = createMemo(() => { + if (!expanded()) return false + if (force()) return false + if (mediaKind()) return false + return changedLines() > MAX_DIFF_CHANGED_LINES + }) - const draftRange = createMemo(() => { - const current = commenting() - if (!current || current.file !== file) return null - return current.range - }) + const isAdded = () => + item().status === "added" || (beforeText().length === 0 && afterText().length > 0) + const isDeleted = () => + item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) - const commentsUi = createLineCommentController({ - comments, - label: i18n.t("ui.lineComment.submit"), - draftKey: () => file, - state: { - opened: () => { - const current = opened() - if (!current || current.file !== file) return null - return current.id + const selectedLines = createMemo(() => { + const current = selection() + if (!current || current.file !== file) return null + return current.range + }) + + const draftRange = createMemo(() => { + const current = commenting() + if (!current || current.file !== file) return null + return current.range + }) + + const commentsUi = createLineCommentController({ + comments, + label: i18n.t("ui.lineComment.submit"), + draftKey: () => file, + state: { + opened: () => { + const current = opened() + if (!current || current.file !== file) return null + return current.id + }, + setOpened: (id) => setOpened(id ? { file, id } : null), + selected: selectedLines, + setSelected: (range) => setSelection(range ? { file, range } : null), + commenting: draftRange, + setCommenting: (range) => setCommenting(range ? { file, range } : null), }, - setOpened: (id) => setOpened(id ? { file, id } : null), - selected: selectedLines, - setSelected: (range) => setSelection(range ? { file, range } : null), - commenting: draftRange, - setCommenting: (range) => setCommenting(range ? { file, range } : null), - }, - getSide: selectionSide, - clearSelectionOnSelectionEndNull: false, - onSubmit: ({ comment, selection }) => { - props.onLineComment?.({ - file, - selection, - comment, - preview: selectionPreview(item(), selection), - }) - }, - onUpdate: ({ id, comment, selection }) => { - props.onLineCommentUpdate?.({ - id, - file, - selection, - comment, - preview: selectionPreview(item(), selection), - }) - }, - onDelete: (comment) => { - props.onLineCommentDelete?.({ - id: comment.id, - file, - }) - }, - editSubmitLabel: props.lineCommentActions?.saveLabel, - renderCommentActions: props.lineCommentActions - ? (comment, controls) => ( - - ) - : undefined, - }) + getSide: selectionSide, + clearSelectionOnSelectionEndNull: false, + onSubmit: ({ comment, selection }) => { + props.onLineComment?.({ + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onUpdate: ({ id, comment, selection }) => { + props.onLineCommentUpdate?.({ + id, + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onDelete: (comment) => { + props.onLineCommentDelete?.({ + id: comment.id, + file, + }) + }, + editSubmitLabel: props.lineCommentActions?.saveLabel, + renderCommentActions: props.lineCommentActions + ? (comment, controls) => ( + + ) + : undefined, + }) - onCleanup(() => { - anchors.delete(file) - readyFiles.delete(file) - searchHandles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - }) + onCleanup(() => { + anchors.delete(file) + readyFiles.delete(file) + searchHandles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + }) - const handleLineSelected = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelected(range) - } + const handleLineSelected = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelected(range) + } - const handleLineSelectionEnd = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelectionEnd(range) - } + const handleLineSelectionEnd = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelectionEnd(range) + } - return ( - - - -
-
- -
- - {`\u202A${getDirectory(file)}\u202C`} - - {getFilename(file)} - - - - - + return ( + + + +
+
+ +
+ + {`\u202A${getDirectory(file)}\u202C`} + + {getFilename(file)} + + + + + +
+
+
+ + +
+ + {i18n.t("ui.sessionReview.change.added")} + + +
+
+ + + {i18n.t("ui.sessionReview.change.removed")} + + + + + {i18n.t("ui.sessionReview.change.modified")} + + + + + +
+ + +
-
+ + + +
{ + wrapper = el + anchors.set(file, el) + }} + > + - -
- - {i18n.t("ui.sessionReview.change.added")} - - + +
+
+ {i18n.t("ui.sessionReview.largeDiff.title")} +
+
+ {i18n.t("ui.sessionReview.largeDiff.meta", { + limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), + current: changedLines().toLocaleString(), + })} +
+
+ +
- - - {i18n.t("ui.sessionReview.change.removed")} - - - - - {i18n.t("ui.sessionReview.change.modified")} - - - + { + readyFiles.add(file) + props.onDiffRendered?.() + }} + enableLineSelection={props.onLineComment != null} + enableHoverUtility={props.onLineComment != null} + onLineSelected={handleLineSelected} + onLineSelectionEnd={handleLineSelectionEnd} + onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} + annotations={commentsUi.annotations()} + renderAnnotation={commentsUi.renderAnnotation} + renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} + selectedLines={selectedLines()} + commentedLines={commentedLines()} + search={{ + shortcuts: "disabled", + showBar: false, + disableVirtualization: searchExpanded(), + register: (handle: FileSearchHandle | null) => { + if (!handle) { + searchHandles.delete(file) + readyFiles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + return + } + + searchHandles.set(file, handle) + }, + }} + before={{ + name: file, + contents: typeof item().before === "string" ? item().before : "", + }} + after={{ + name: file, + contents: typeof item().after === "string" ? item().after : "", + }} + media={{ + mode: "auto", + path: file, + before: item().before, + after: item().after, + readFile: props.readFile, + }} + /> - - - -
+
- - - -
{ - wrapper = el - anchors.set(file, el) - }} - > - - - -
-
- {i18n.t("ui.sessionReview.largeDiff.title")} -
-
- {i18n.t("ui.sessionReview.largeDiff.meta", { - limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), - current: changedLines().toLocaleString(), - })} -
-
- -
-
-
- - { - readyFiles.add(file) - props.onDiffRendered?.() - }} - enableLineSelection={props.onLineComment != null} - enableHoverUtility={props.onLineComment != null} - onLineSelected={handleLineSelected} - onLineSelectionEnd={handleLineSelectionEnd} - onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} - annotations={commentsUi.annotations()} - renderAnnotation={commentsUi.renderAnnotation} - renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} - selectedLines={selectedLines()} - commentedLines={commentedLines()} - search={{ - shortcuts: "disabled", - showBar: false, - disableVirtualization: searchExpanded(), - register: (handle: FileSearchHandle | null) => { - if (!handle) { - searchHandles.delete(file) - readyFiles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - return - } - - searchHandles.set(file, handle) - }, - }} - before={{ - name: file, - contents: typeof item().before === "string" ? item().before : "", - }} - after={{ - name: file, - contents: typeof item().after === "string" ? item().after : "", - }} - media={{ - mode: "auto", - path: file, - before: item().before, - after: item().after, - readFile: props.readFile, - }} - /> - -
-
-
-
- - ) - }} - - - -
- + +
+ ) + }} + + + +
+ +
) }