mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
chore: merge beta into MCP review
This commit is contained in:
@@ -148,6 +148,7 @@ const scenarios = [
|
||||
for (const scenario of scenarios) {
|
||||
for (const [width, direction] of [
|
||||
[1400, "ltr"],
|
||||
[900, "ltr"],
|
||||
[390, "rtl"],
|
||||
] as const) {
|
||||
test(`${scenario.name}: file headers stay flush at ${width}px in ${direction}`, async ({ page }, info) => {
|
||||
@@ -176,7 +177,7 @@ for (const scenario of scenarios) {
|
||||
const scroller = page.locator('[data-slot="session-timeline-scroll"] .scroll-view__viewport')
|
||||
const toolTitle = scroller.locator('[data-slot="collapsible-trigger"][data-locked]')
|
||||
await expect(toolTitle).toHaveCount(scenario.title ? 1 : 0)
|
||||
await expect(scroller.locator("[data-session-title]")).toHaveCount(width === 1400 ? 1 : 0)
|
||||
await expect(scroller.locator("[data-session-title]")).toHaveCount(width >= 768 ? 1 : 0)
|
||||
|
||||
for (const file of scenario.files) {
|
||||
const name = new RegExp(`${file}\\.ts`)
|
||||
|
||||
@@ -9,6 +9,7 @@ const tabs = (page: Page) => page.locator('[data-slot="titlebar-tabs"] a')
|
||||
|
||||
for (const direction of ["ltr", "rtl"] as const) {
|
||||
test(`session header groups controls and exposes session details in ${direction}`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
await mockStressTimeline(page)
|
||||
await seed(page, {
|
||||
tabs: [fixture.sourceID, fixture.targetID],
|
||||
@@ -65,6 +66,36 @@ for (const direction of ["ltr", "rtl"] as const) {
|
||||
await header.getByRole("textbox").press("Escape")
|
||||
await expect(header.getByRole("heading")).toHaveText(fixture.expected.targetTitle)
|
||||
|
||||
for (const width of [900, 1440]) {
|
||||
await page.setViewportSize({ width, height: 900 })
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const [location, title, menu, summary, sidebar] = await Promise.all(
|
||||
[project, header.getByRole("heading"), more, details, review].map((control) => control.boundingBox()),
|
||||
)
|
||||
|
||||
if (!location || !title || !menu || !summary || !sidebar) return false
|
||||
const gap = direction === "ltr" ? menu.x - title.x - title.width : title.x - menu.x - menu.width
|
||||
|
||||
const aligned =
|
||||
Math.abs(menu.y + menu.height / 2 - summary.y - summary.height / 2) <= 0.5 &&
|
||||
Math.abs(menu.y + menu.height / 2 - sidebar.y - sidebar.height / 2) <= 0.5
|
||||
|
||||
return (
|
||||
Math.abs(gap - 2) <= 0.5 &&
|
||||
aligned &&
|
||||
(width === 900
|
||||
? Math.abs(location.y - menu.y - menu.height - 8) <= 0.5
|
||||
: Math.abs(location.y + location.height / 2 - menu.y - menu.height / 2) <= 0.5)
|
||||
)
|
||||
})
|
||||
.toBe(true)
|
||||
|
||||
const screenshot = test.info().outputPath(`header-${width}.png`)
|
||||
await page.screenshot({ path: screenshot })
|
||||
await test.info().attach(`header-${width}`, { path: screenshot, contentType: "image/png" })
|
||||
}
|
||||
|
||||
await review.click()
|
||||
await expect(review).toHaveAttribute("aria-expanded", "true")
|
||||
await expect(page.locator("#review-panel")).toBeVisible()
|
||||
|
||||
@@ -153,14 +153,14 @@ test("keeps the active nested session visible in a narrow desktop header", async
|
||||
const separator = header.locator('[data-slot="session-title-ancestors"] + [data-slot="session-title-separator"]')
|
||||
await expect(header.locator('[data-slot="session-title-ancestor"]')).toHaveText([parentTitle, childTitle])
|
||||
await expect(heading).toBeInViewport()
|
||||
await expect(separator).toBeInViewport()
|
||||
await expect(separator).toBeHidden()
|
||||
const bounds = await heading.boundingBox()
|
||||
expect(bounds).not.toBeNull()
|
||||
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(820)
|
||||
|
||||
await page.evaluate(() => document.documentElement.setAttribute("dir", "rtl"))
|
||||
await expect(heading).toBeInViewport()
|
||||
await expect(separator).toBeInViewport()
|
||||
await expect(separator).toBeHidden()
|
||||
|
||||
const root = header.locator(`[data-slot="session-title-ancestor"][data-session-id="${parentID}"]`)
|
||||
await root.focus()
|
||||
|
||||
@@ -1,3 +1,91 @@
|
||||
.session-options-menu [data-component="menu-v2-item"] {
|
||||
padding-inline-end: 30px;
|
||||
}
|
||||
|
||||
.session-header-layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
inline-size: 100%;
|
||||
block-size: 48px;
|
||||
}
|
||||
|
||||
.session-header-location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-inline-size: 0;
|
||||
max-inline-size: 60%;
|
||||
}
|
||||
|
||||
.session-header-location > [data-slot="session-location-trigger"] {
|
||||
max-inline-size: 100%;
|
||||
}
|
||||
|
||||
.session-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.session-header-title > [data-slot="session-title-child"] {
|
||||
min-inline-size: 0;
|
||||
max-inline-size: 100%;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.session-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
margin-inline-start: 6px;
|
||||
}
|
||||
|
||||
.session-header-fade {
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
@container (max-width: 1000px) {
|
||||
.session-header-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
block-size: 80px;
|
||||
padding-block: 10px;
|
||||
}
|
||||
|
||||
.session-header-title {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
block-size: 28px;
|
||||
}
|
||||
|
||||
.session-header-actions {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.session-header-location {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
max-inline-size: 100%;
|
||||
block-size: 24px;
|
||||
}
|
||||
|
||||
.session-header-location > [data-slot="session-title-separator"]:last-child,
|
||||
.session-header-location > div > [data-slot="session-title-separator"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.session-header-fade {
|
||||
top: 80px;
|
||||
}
|
||||
|
||||
[data-slot="session-timeline-scroll"] {
|
||||
--session-header-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ProjectAvatar,
|
||||
} from "@opencode/ui/project-avatar"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { createMemo, For, Show, type ComponentProps, type ParentProps } from "solid-js"
|
||||
import { createMemo, For, Show, type ComponentProps, type JSX, type ParentProps } from "solid-js"
|
||||
import { containsDirectory, getFilename } from "@opencode/util/path"
|
||||
import { useServer } from "@/runtime/server/current"
|
||||
import { ServerConnection, useServers } from "@/runtime/server/registry"
|
||||
@@ -42,12 +42,24 @@ export function SessionTitleHeader(props: ParentProps) {
|
||||
{/* Stuck timeline headers stack above this fade, so it only fades content scrolling under the bar. */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none sticky top-12 z-[5] -mt-4 h-4 w-full bg-[linear-gradient(to_bottom,var(--v2-background-bg-base),transparent)]"
|
||||
class="session-header-fade pointer-events-none sticky z-[5] -mt-4 h-4 w-full bg-[linear-gradient(to_bottom,var(--v2-background-bg-base),transparent)]"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function SessionHeaderLayout(props: ParentProps<{ location: JSX.Element; actions?: JSX.Element }>) {
|
||||
return (
|
||||
<div class="session-header-layout">
|
||||
<div class="session-header-location">{props.location}</div>
|
||||
<div class="session-header-title">{props.children}</div>
|
||||
<Show when={props.actions}>
|
||||
<div class="session-header-actions">{props.actions}</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the session runs: the project and its worktree. Opens a card with the server, the full path and the location's
|
||||
* actions.
|
||||
@@ -163,10 +175,7 @@ export function SessionLocation(props: {
|
||||
<Show when={!props.compact && worktree()}>
|
||||
{(name) => (
|
||||
<>
|
||||
<span
|
||||
class="shrink-0 ps-1 pe-0.5 text-[11px] font-medium text-v2-text-text-faint"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="shrink-0 ps-1 pe-0.5 text-[11px] font-medium text-v2-text-text-faint" aria-hidden="true">
|
||||
/
|
||||
</span>
|
||||
<Icon name="outline-worktree" class="shrink-0 text-v2-icon-icon-muted" />
|
||||
@@ -432,9 +441,9 @@ export function SessionIdentityHeader(props: { sessionID: string; session?: Sess
|
||||
return (
|
||||
<Show when={title() || parentTitle()}>
|
||||
<SessionTitleHeader>
|
||||
<div class="flex h-12 w-full items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-1">
|
||||
<div class="flex min-w-0 w-full flex-1 items-center gap-0.5">
|
||||
<SessionHeaderLayout
|
||||
location={
|
||||
<>
|
||||
<SessionLocation
|
||||
project={project()}
|
||||
directory={directory()}
|
||||
@@ -451,22 +460,23 @@ export function SessionIdentityHeader(props: { sessionID: string; session?: Sess
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<SessionWorkingIndicator sessionID={props.sessionID} />
|
||||
<Show when={title()}>
|
||||
{(value) => (
|
||||
<h1
|
||||
data-slot={parentID() ? "session-title-child" : undefined}
|
||||
dir="auto"
|
||||
class="w-fit truncate rounded-[6px] px-1 py-1 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
>
|
||||
{value()}
|
||||
</h1>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<SessionWorkingIndicator sessionID={props.sessionID} />
|
||||
<Show when={title()}>
|
||||
{(value) => (
|
||||
<h1
|
||||
data-slot={parentID() ? "session-title-child" : undefined}
|
||||
dir="auto"
|
||||
class="w-fit truncate rounded-[6px] px-1 py-1 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
>
|
||||
{value()}
|
||||
</h1>
|
||||
)}
|
||||
</Show>
|
||||
</SessionHeaderLayout>
|
||||
</SessionTitleHeader>
|
||||
</Show>
|
||||
)
|
||||
|
||||
@@ -25,7 +25,12 @@ import { isWorkspaceDirectory } from "@/workspaces/paths"
|
||||
import { parseCommentNote, readPromptPresentation } from "@/composer/comment-note"
|
||||
import { parseVoiceDelegation } from "./voice-delegation"
|
||||
import { useCommand } from "@/shell/commands/command"
|
||||
import { SessionAncestorTrail, SessionLocation, SessionTitleHeader } from "../session-identity-header"
|
||||
import {
|
||||
SessionAncestorTrail,
|
||||
SessionHeaderLayout,
|
||||
SessionLocation,
|
||||
SessionTitleHeader,
|
||||
} from "../session-identity-header"
|
||||
import { SessionHeaderSpacer } from "@/session/header/session-header"
|
||||
import { SessionWorkingIndicator } from "@/session/header/session-working-indicator"
|
||||
|
||||
@@ -451,9 +456,9 @@ function MessageTimelineView(
|
||||
header={
|
||||
<Show when={!props.hideHeader}>
|
||||
<SessionTitleHeader>
|
||||
<div class="h-12 w-full flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-1 min-w-0 flex-1">
|
||||
<div class="flex items-center gap-0.5 min-w-0 flex-1 w-full">
|
||||
<SessionHeaderLayout
|
||||
location={
|
||||
<>
|
||||
<SessionLocation
|
||||
project={headerProject()}
|
||||
directory={sessionDirectory()}
|
||||
@@ -470,148 +475,151 @@ function MessageTimelineView(
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={parentID() && childTitle()}>
|
||||
{(value) => (
|
||||
<>
|
||||
<SessionWorkingIndicator sessionID={sessionID()} />
|
||||
<h1
|
||||
data-slot="session-title-child"
|
||||
dir="auto"
|
||||
class="max-w-[45%] shrink-0 w-fit truncate rounded-[6px] px-1 py-1 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base"
|
||||
>
|
||||
{value()}
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={!parentID() && (childTitle() || title.editing)}>
|
||||
<Show
|
||||
when={title.editing}
|
||||
fallback={
|
||||
<h1
|
||||
data-slot="session-title-child"
|
||||
class="truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base w-fit rounded-[6px] px-1 py-1 hover:bg-v2-overlay-simple-overlay-hover"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
onClick={openTitleEditor}
|
||||
>
|
||||
{childTitle()}
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<InlineInput
|
||||
ref={(el) => {
|
||||
titleRef = el
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<Show when={sessionID()} keyed>
|
||||
{(_id) => (
|
||||
<>
|
||||
{props.search}
|
||||
<ExtensionSlot
|
||||
at="session.header"
|
||||
input={{
|
||||
get session() {
|
||||
return props.view
|
||||
},
|
||||
get screen() {
|
||||
return props.screen
|
||||
},
|
||||
get active() {
|
||||
return props.active !== false
|
||||
},
|
||||
}}
|
||||
data-slot="session-title-child"
|
||||
dir="auto"
|
||||
value={title.draft}
|
||||
disabled={props.pending.rename()}
|
||||
class="block text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base field-sizing-content rounded-[6px] px-1 py-1"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
style={{
|
||||
"--inline-input-shadow": "none",
|
||||
"text-align": "start",
|
||||
}}
|
||||
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
if (event.isComposing || event.keyCode === 229) return
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void saveTitleEditor()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault()
|
||||
closeTitleEditor()
|
||||
}
|
||||
}}
|
||||
onBlur={() => void saveTitleEditor()}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={!parentID() && sessionID()} keyed>
|
||||
{(id) => (
|
||||
<Menu
|
||||
gutter={6}
|
||||
placement="bottom-start"
|
||||
open={title.menuOpen}
|
||||
onOpenChange={(open) => setTitle("menuOpen", open)}
|
||||
>
|
||||
<Menu.Trigger
|
||||
as={IconButton}
|
||||
icon={<Icon name="outline-dots" />}
|
||||
variant="ghost-muted"
|
||||
size="large"
|
||||
class="shrink-0"
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
aria-expanded={title.menuOpen}
|
||||
/>
|
||||
<Menu.Portal>
|
||||
<Menu.Content
|
||||
class="session-options-menu w-max"
|
||||
style={{ "min-width": "0" }}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!title.pendingRename) return
|
||||
event.preventDefault()
|
||||
setTitle("pendingRename", false)
|
||||
openTitleEditor()
|
||||
}}
|
||||
>
|
||||
<Show when={!parentID()}>
|
||||
<Menu.Item
|
||||
onSelect={() => {
|
||||
setTitle("pendingRename", true)
|
||||
setTitle("menuOpen", false)
|
||||
}}
|
||||
>
|
||||
{language.t("common.rename")}
|
||||
</Menu.Item>
|
||||
<Menu.Item onSelect={() => void props.action.export(id)}>
|
||||
{language.t("common.export")}…
|
||||
</Menu.Item>
|
||||
</Show>
|
||||
<Show when={!parentID()}>
|
||||
{/* TODO: Need a session archive API. */}
|
||||
<Menu.Separator />
|
||||
<Menu.Item onSelect={() => props.action.showDelete(id)}>
|
||||
{language.t("common.delete")}…
|
||||
</Menu.Item>
|
||||
</Show>
|
||||
</Menu.Content>
|
||||
</Menu.Portal>
|
||||
</Menu>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={sessionID()} keyed>
|
||||
{(_id) => (
|
||||
<div class="shrink-0 flex items-center gap-2">
|
||||
{props.search}
|
||||
<ExtensionSlot
|
||||
at="session.header"
|
||||
input={{
|
||||
get session() {
|
||||
return props.view
|
||||
},
|
||||
get screen() {
|
||||
return props.screen
|
||||
},
|
||||
get active() {
|
||||
return props.active !== false
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<SessionHeaderSpacer visible={props.reserveReviewToggle} />
|
||||
</div>
|
||||
<SessionHeaderSpacer visible={props.reserveReviewToggle} />
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<Show when={parentID() && childTitle()}>
|
||||
{(value) => (
|
||||
<>
|
||||
<SessionWorkingIndicator sessionID={sessionID()} />
|
||||
<h1
|
||||
data-slot="session-title-child"
|
||||
dir="auto"
|
||||
class="max-w-[45%] shrink-0 w-fit truncate rounded-[6px] px-1 py-1 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base"
|
||||
>
|
||||
{value()}
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={!parentID() && (childTitle() || title.editing)}>
|
||||
<Show
|
||||
when={title.editing}
|
||||
fallback={
|
||||
<h1
|
||||
data-slot="session-title-child"
|
||||
class="truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base w-fit rounded-[6px] px-1 py-1 hover:bg-v2-overlay-simple-overlay-hover"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
onClick={openTitleEditor}
|
||||
>
|
||||
{childTitle()}
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<InlineInput
|
||||
ref={(el) => {
|
||||
titleRef = el
|
||||
}}
|
||||
data-slot="session-title-child"
|
||||
dir="auto"
|
||||
value={title.draft}
|
||||
disabled={props.pending.rename()}
|
||||
class="block text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base field-sizing-content rounded-[6px] px-1 py-1"
|
||||
classList={{ "max-w-[45%] shrink-0": !!parentID() }}
|
||||
style={{
|
||||
"--inline-input-shadow": "none",
|
||||
"text-align": "start",
|
||||
}}
|
||||
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
if (event.isComposing || event.keyCode === 229) return
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void saveTitleEditor()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault()
|
||||
closeTitleEditor()
|
||||
}
|
||||
}}
|
||||
onBlur={() => void saveTitleEditor()}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={!parentID() && sessionID()} keyed>
|
||||
{(id) => (
|
||||
<Menu
|
||||
gutter={6}
|
||||
placement="bottom-start"
|
||||
open={title.menuOpen}
|
||||
onOpenChange={(open) => setTitle("menuOpen", open)}
|
||||
>
|
||||
<Menu.Trigger
|
||||
as={IconButton}
|
||||
icon={<Icon name="outline-dots" />}
|
||||
variant="ghost-muted"
|
||||
size="large"
|
||||
class="shrink-0"
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
aria-expanded={title.menuOpen}
|
||||
/>
|
||||
<Menu.Portal>
|
||||
<Menu.Content
|
||||
class="session-options-menu w-max"
|
||||
style={{ "min-width": "0" }}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!title.pendingRename) return
|
||||
event.preventDefault()
|
||||
setTitle("pendingRename", false)
|
||||
openTitleEditor()
|
||||
}}
|
||||
>
|
||||
<Show when={!parentID()}>
|
||||
<Menu.Item
|
||||
onSelect={() => {
|
||||
setTitle("pendingRename", true)
|
||||
setTitle("menuOpen", false)
|
||||
}}
|
||||
>
|
||||
{language.t("common.rename")}
|
||||
</Menu.Item>
|
||||
<Menu.Item onSelect={() => void props.action.export(id)}>
|
||||
{language.t("common.export")}…
|
||||
</Menu.Item>
|
||||
</Show>
|
||||
<Show when={!parentID()}>
|
||||
{/* TODO: Need a session archive API. */}
|
||||
<Menu.Separator />
|
||||
<Menu.Item onSelect={() => props.action.showDelete(id)}>
|
||||
{language.t("common.delete")}…
|
||||
</Menu.Item>
|
||||
</Show>
|
||||
</Menu.Content>
|
||||
</Menu.Portal>
|
||||
</Menu>
|
||||
)}
|
||||
</Show>
|
||||
</SessionHeaderLayout>
|
||||
</SessionTitleHeader>
|
||||
</Show>
|
||||
}
|
||||
|
||||
@@ -924,7 +924,7 @@ export function createTimelineVirtualizer(input: Input) {
|
||||
onScroll={handleListScroll}
|
||||
onClick={input.onSelectionInteraction}
|
||||
class="relative min-w-0 w-full h-full"
|
||||
style={{ "--sticky-accordion-top": input.showHeader() ? "48px" : "0px" }}
|
||||
style={{ "--sticky-accordion-top": input.showHeader() ? "var(--session-header-height, 48px)" : "0px" }}
|
||||
>
|
||||
<Show when={input.showHeader()} fallback={<div aria-hidden="true" class="h-4 md:hidden" />}>
|
||||
{props.header}
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
[data-slot="session-review-change"] {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-weight: 440;
|
||||
}
|
||||
|
||||
[data-slot="session-review-summary-open-file"] {
|
||||
@@ -207,11 +207,11 @@
|
||||
}
|
||||
|
||||
[data-slot="session-review-change"][data-type="added"] {
|
||||
color: var(--icon-diff-add-base);
|
||||
color: var(--text-diff-add-base);
|
||||
}
|
||||
|
||||
[data-slot="session-review-change"][data-type="removed"] {
|
||||
color: var(--icon-diff-delete-base);
|
||||
color: var(--text-diff-delete-base);
|
||||
}
|
||||
|
||||
[data-slot="session-review-change"][data-type="modified"] {
|
||||
|
||||
@@ -633,7 +633,7 @@ export function createSessionTimelineRowRenderer(input: {
|
||||
<Icon
|
||||
name={outcomeNoticeIcon(outcome())}
|
||||
size="normal"
|
||||
class={`inline-block align-top me-2 ${outcomeNoticeColor(outcome())}`}
|
||||
class={`inline-block align-top me-1.5 ${outcomeNoticeColor(outcome())}`}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
@@ -1064,7 +1064,7 @@ function outcomeNoticeIcon(outcome: Outcome) {
|
||||
|
||||
if (outcome === "cancelled") return "circle-ban-sign"
|
||||
|
||||
return "circle-check"
|
||||
return "check"
|
||||
}
|
||||
|
||||
function outcomeNoticeColor(outcome: Outcome | undefined) {
|
||||
@@ -1074,7 +1074,7 @@ function outcomeNoticeColor(outcome: Outcome | undefined) {
|
||||
|
||||
if (outcome === "cancelled") return "text-v2-state-fg-warning"
|
||||
|
||||
return "text-v2-state-fg-info"
|
||||
return "text-v2-text-text-code-accent"
|
||||
}
|
||||
|
||||
function outcomeNoticeKey(state: string | undefined) {
|
||||
|
||||
Reference in New Issue
Block a user