mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 20:52:01 +03:00
feat(app): match the session column to the design and use pretty text wrapping (#54020)
This commit is contained in:
committed by
GitHub
Unverified
parent
1aae67e853
commit
f9e892dc85
@@ -215,6 +215,7 @@ for (const direction of ["ltr", "rtl"] as const) {
|
||||
)
|
||||
|
||||
const composer = page.locator('[data-component="session-composer-dock"] > div')
|
||||
const timeline = page.locator('[data-slot="session-timeline-scroll"]')
|
||||
const summary = page.getByRole("dialog", { name: "Session details", exact: true })
|
||||
await expect(row).toBeInViewport()
|
||||
const before = { row: (await row.boundingBox())!, composer: (await composer.boundingBox())! }
|
||||
@@ -229,12 +230,13 @@ for (const direction of ["ltr", "rtl"] as const) {
|
||||
return Math.max(Math.abs(message.x - before.row.x), Math.abs(input.x - before.composer.x))
|
||||
})
|
||||
.toBeLessThan(1)
|
||||
// The capped session column can end before the panel, so the overlay is measured against the whole timeline.
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const message = (await row.boundingBox())!
|
||||
const view = (await timeline.boundingBox())!
|
||||
const details = (await summary.boundingBox())!
|
||||
|
||||
return Math.min(message.x + message.width, details.x + details.width) - Math.max(message.x, details.x)
|
||||
return Math.min(view.x + view.width, details.x + details.width) - Math.max(view.x, details.x)
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
const text = summary.getByText(branch, { exact: true })
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createAnimatedPresence } from "@/runtime/animated-presence"
|
||||
export function ComposerDropzone(props: {
|
||||
active: boolean
|
||||
input?: { image?: boolean; pdf?: boolean }
|
||||
identity?: () => unknown
|
||||
identity?: () => string | undefined
|
||||
}) {
|
||||
const language = useLanguage()
|
||||
const [elements, setElements] = createStore<{ dropzone?: HTMLDivElement }>({})
|
||||
@@ -52,7 +52,7 @@ export function ComposerDropzone(props: {
|
||||
>
|
||||
<div class="absolute inset-0 bg-v2-background-bg-base/25" />
|
||||
<div
|
||||
class="absolute inset-y-0 left-1/2 w-full -translate-x-1/2 md:max-w-200 2xl:max-w-[1000px]"
|
||||
class="absolute inset-y-0 left-1/2 w-full -translate-x-1/2 md:max-w-session"
|
||||
style={{
|
||||
"-webkit-mask-image": "linear-gradient(to right, transparent 0%, black 12%, black 88%, transparent 100%)",
|
||||
"mask-image": "linear-gradient(to right, transparent 0%, black 12%, black 88%, transparent 100%)",
|
||||
|
||||
@@ -31,7 +31,7 @@ export function SessionComposerRegion(props: {
|
||||
<div
|
||||
classList={{
|
||||
"w-full px-3 pointer-events-auto": true,
|
||||
"md:max-w-[1000px] md:mx-auto": controller.centered(),
|
||||
"md:max-w-session md:mx-auto": controller.centered(),
|
||||
}}
|
||||
>
|
||||
<Show when={controller.state.websearch.request()}>
|
||||
|
||||
@@ -47,7 +47,7 @@ export function PreparingComposer(props: { pending: PendingSession }) {
|
||||
|
||||
return (
|
||||
<div data-component="session-composer-dock" class="w-full shrink-0 bg-v2-background-bg-base pb-3">
|
||||
<div class="mx-auto w-full max-w-[1000px] px-3">
|
||||
<div class="mx-auto w-full px-3 md:max-w-session">
|
||||
<ComposerEditor controller={editor} modelControlsVisible={false} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@ function PreparingSession(props: { sessionID: string; pending: PendingSession })
|
||||
>
|
||||
<div data-component="session-preparing" data-workspace-session class="min-h-0 flex-1 overflow-y-auto">
|
||||
<SessionIdentityHeader sessionID={props.sessionID} />
|
||||
<div class="mx-auto w-full min-w-0 max-w-[1000px] px-4 pb-5 md:px-5">
|
||||
<div class="mx-auto w-full min-w-0 px-4 pb-5 md:max-w-session md:px-6">
|
||||
<SessionUserMessage
|
||||
sessionID={props.sessionID}
|
||||
message={props.pending.message}
|
||||
|
||||
@@ -160,7 +160,7 @@ function MessageTimelineView(
|
||||
if (!directory) return
|
||||
void data.location.vcs.sync({ directory }).catch(() => undefined)
|
||||
})
|
||||
const turnPadding = () => "px-4 md:px-5"
|
||||
const turnPadding = () => "px-4 md:px-6"
|
||||
const showHeader = createMemo(() => !props.hideHeader && (props.data.showHeader() || workspaceSession()))
|
||||
const pinned = createMemo(() => props.pinned)
|
||||
const messageByID = projection.messageByID
|
||||
@@ -410,7 +410,7 @@ function MessageTimelineView(
|
||||
<div
|
||||
classList={{
|
||||
"min-w-0 w-full max-w-full": true,
|
||||
"md:max-w-[1000px] md:mx-auto": props.centered,
|
||||
"md:max-w-session md:mx-auto": props.centered,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Pretty wrapping would move settled words as each token arrives, so the growing block stays greedy until it completes. */
|
||||
&[data-streaming] > [data-markdown-block]:last-child {
|
||||
text-wrap-style: auto;
|
||||
}
|
||||
|
||||
/* Headings: Sized by level, distinguished by color and spacing */
|
||||
h1 {
|
||||
font-size: 17px;
|
||||
|
||||
@@ -817,6 +817,7 @@ export function Markdown(
|
||||
return (
|
||||
<div
|
||||
data-component="markdown"
|
||||
data-streaming={local.streaming ? "" : undefined}
|
||||
dir="auto"
|
||||
classList={{
|
||||
...local.classList,
|
||||
|
||||
@@ -22,6 +22,8 @@ const unsafeCSS = `
|
||||
color-mix(in lab, var(--diffs-bg) 98%, var(--diffs-mixer)),
|
||||
color-mix(in lab, var(--diffs-bg) 95%, var(--diffs-mixer))
|
||||
);
|
||||
/* The app's code wrapping rules do not reach this shadow tree, and pretty is inherited from the page. */
|
||||
text-wrap-style: auto;
|
||||
}
|
||||
|
||||
[data-diff],
|
||||
|
||||
@@ -140,7 +140,7 @@ export function createSessionTimelineRowRenderer(input: {
|
||||
: undefined
|
||||
}
|
||||
|
||||
const padding = () => input.padding?.() ?? "px-4 md:px-5"
|
||||
const padding = () => input.padding?.() ?? "px-4 md:px-6"
|
||||
|
||||
const indexGroupContents = (refs: PartRef[]) => {
|
||||
const result = new Map<string, Map<string, SessionMessageAssistant["content"][number]>>()
|
||||
@@ -470,7 +470,7 @@ export function createSessionTimelineRowRenderer(input: {
|
||||
data-timeline-spacing={Predicate.isTagged(props.row, "AssistantPart") ? props.row.spacing : undefined}
|
||||
classList={{
|
||||
"min-w-0 w-full max-w-full": true,
|
||||
"md:max-w-[1000px] md:mx-auto": input.centered?.(),
|
||||
"md:max-w-session md:mx-auto": input.centered?.(),
|
||||
"pt-2": Predicate.isTagged(props.row, "AssistantPart") && props.row.spacing === "tool",
|
||||
"pt-4": Predicate.isTagged(props.row, "AssistantPart") && props.row.spacing === "content",
|
||||
"pt-6": Predicate.isTagged(props.row, "Error"),
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
7. Disable tap highlights on iOS.
|
||||
8. Avoid one-word last lines in all wrapped text. Chromium keeps the line count, so heights do not change.
|
||||
*/
|
||||
|
||||
html,
|
||||
@@ -34,6 +35,7 @@ html,
|
||||
font-feature-settings: var(--font-family-sans--font-feature-settings, normal); /* 5 */
|
||||
font-variation-settings: var(--font-family-sans--font-variation-settings, normal); /* 6 */
|
||||
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||
text-wrap-style: pretty; /* 8 */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -111,6 +113,7 @@ strong {
|
||||
2. Use the user's configured `mono` font-feature-settings by default.
|
||||
3. Use the user's configured `mono` font-variation-settings by default.
|
||||
4. Correct the odd `em` font sizing in all browsers.
|
||||
5. Keep code wrapping greedy so wrapped lines break where an editor would.
|
||||
*/
|
||||
|
||||
code,
|
||||
@@ -121,6 +124,17 @@ pre {
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings, normal); /* 2 */
|
||||
font-variation-settings: var(--font-family-mono--font-variation-settings, normal); /* 3 */
|
||||
font-size: 1em; /* 4 */
|
||||
text-wrap-style: auto; /* 5 */
|
||||
}
|
||||
|
||||
/*
|
||||
Keep earlier lines still while the user types.
|
||||
*/
|
||||
|
||||
input,
|
||||
textarea,
|
||||
[contenteditable] {
|
||||
text-wrap-style: stable;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
/* Shared by the session timeline, composer dock, and drop target so their edges stay aligned.
|
||||
The composer dock's 12px gutter leaves the design's 720px composer. */
|
||||
--container-session: 744px;
|
||||
|
||||
--font-sans: var(--font-family-sans);
|
||||
--font-sans--font-feature-settings: var(--font-family-sans--font-feature-settings);
|
||||
|
||||
Reference in New Issue
Block a user