mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
fix(app): restore staging render path and nested scroll boundary checks
This commit is contained in:
@@ -23,23 +23,25 @@ describe("shouldMarkBoundaryGesture", () => {
|
||||
scrollTop: 0,
|
||||
scrollHeight: 300,
|
||||
clientHeight: 300,
|
||||
mode: "normal",
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("marks when scrolling beyond top boundary", () => {
|
||||
// column-reverse: scrollTop=-590 means 590px from bottom (10px from top, max=600)
|
||||
test("marks when scrolling beyond top boundary in reversed mode", () => {
|
||||
// column-reverse: scrollTop=-590 means 10px from top (max=600)
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
delta: -40,
|
||||
scrollTop: -590,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "reversed",
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("marks when scrolling beyond bottom boundary", () => {
|
||||
test("marks when scrolling beyond bottom boundary in reversed mode", () => {
|
||||
// column-reverse: scrollTop=-20 means 20px from bottom
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
@@ -47,18 +49,55 @@ describe("shouldMarkBoundaryGesture", () => {
|
||||
scrollTop: -20,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "reversed",
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("does not mark when nested scroller can consume movement", () => {
|
||||
// column-reverse: scrollTop=-400 means 400px from bottom (middle of scroll)
|
||||
test("does not mark when reversed scroller can consume movement", () => {
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
delta: 20,
|
||||
scrollTop: -400,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "reversed",
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
test("marks when scrolling beyond top boundary in normal mode", () => {
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
delta: -40,
|
||||
scrollTop: 10,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "normal",
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("marks when scrolling beyond bottom boundary in normal mode", () => {
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
delta: 50,
|
||||
scrollTop: 580,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "normal",
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("does not mark when normal scroller can consume movement", () => {
|
||||
expect(
|
||||
shouldMarkBoundaryGesture({
|
||||
delta: 20,
|
||||
scrollTop: 300,
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
mode: "normal",
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
@@ -9,14 +9,22 @@ export const shouldMarkBoundaryGesture = (input: {
|
||||
scrollTop: number
|
||||
scrollHeight: number
|
||||
clientHeight: number
|
||||
mode?: "reversed" | "normal"
|
||||
}) => {
|
||||
const max = input.scrollHeight - input.clientHeight
|
||||
if (max <= 1) return true
|
||||
if (!input.delta) return false
|
||||
|
||||
// With column-reverse: scrollTop=0 at bottom, -max at top
|
||||
if (input.delta < 0) return input.scrollTop + input.delta <= -max
|
||||
const mode = input.mode ?? "reversed"
|
||||
if (mode === "normal") {
|
||||
const top = Math.max(0, Math.min(max, input.scrollTop))
|
||||
if (input.delta < 0) return -input.delta > top
|
||||
const bottom = max - top
|
||||
return input.delta > bottom
|
||||
}
|
||||
|
||||
const remaining = -input.scrollTop // distance from bottom
|
||||
return input.delta > remaining
|
||||
const top = max + Math.max(-max, Math.min(0, input.scrollTop))
|
||||
if (input.delta < 0) return -input.delta > top
|
||||
const bottom = -Math.max(-max, Math.min(0, input.scrollTop))
|
||||
return input.delta > bottom
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
|
||||
return nested
|
||||
}
|
||||
|
||||
const boundaryMode = (root: HTMLDivElement, target: HTMLElement) => {
|
||||
if (target === root) return "reversed" as const
|
||||
if (target.dataset.scrollDirection === "reversed") return "reversed" as const
|
||||
return "normal" as const
|
||||
}
|
||||
|
||||
const markBoundaryGesture = (input: {
|
||||
root: HTMLDivElement
|
||||
target: EventTarget | null
|
||||
@@ -90,6 +96,7 @@ const markBoundaryGesture = (input: {
|
||||
scrollTop: target.scrollTop,
|
||||
scrollHeight: target.scrollHeight,
|
||||
clientHeight: target.clientHeight,
|
||||
mode: boundaryMode(input.root, target),
|
||||
})
|
||||
) {
|
||||
input.onMarkScrollGesture(input.root)
|
||||
@@ -157,15 +164,19 @@ function createTimelineStaging(input: TimelineStageInput) {
|
||||
on(
|
||||
() => [input.sessionKey(), input.turnStart() > 0, input.messages().length] as const,
|
||||
([sessionKey, isWindowed, total]) => {
|
||||
cancel()
|
||||
const switched = active !== sessionKey
|
||||
if (switched) {
|
||||
active = sessionKey
|
||||
setReadySession("")
|
||||
}
|
||||
|
||||
const staging = state.activeSession === sessionKey && state.completedSession !== sessionKey
|
||||
if (staging && !switched) return
|
||||
const shouldStage = isWindowed && total > input.config.init && state.completedSession !== sessionKey
|
||||
|
||||
if (staging && !switched && shouldStage && frame !== undefined) return
|
||||
|
||||
cancel()
|
||||
|
||||
if (shouldStage) setReadySession("")
|
||||
if (!shouldStage) {
|
||||
setState({
|
||||
@@ -182,6 +193,7 @@ function createTimelineStaging(input: TimelineStageInput) {
|
||||
}
|
||||
|
||||
let count = Math.min(total, input.config.init)
|
||||
if (staging) count = Math.min(total, Math.max(count, state.count))
|
||||
setState({ activeSession: sessionKey, count })
|
||||
|
||||
const step = () => {
|
||||
@@ -252,7 +264,6 @@ export function MessageTimeline(props: {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
|
||||
const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id))
|
||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||
const sessionID = createMemo(() => params.id)
|
||||
const sessionMessages = createMemo(() => {
|
||||
@@ -304,6 +315,7 @@ export function MessageTimeline(props: {
|
||||
messages: () => props.renderedUserMessages,
|
||||
config: stageCfg,
|
||||
})
|
||||
const rendered = createMemo(() => staging.messages().map((message) => message.id))
|
||||
|
||||
const [title, setTitle] = createStore({
|
||||
draft: "",
|
||||
|
||||
Reference in New Issue
Block a user