summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-02 14:44:05 +0530
committerGitHub <[email protected]>2026-04-02 17:14:05 +0800
commitd540d363a76909c9c1b1d4e4113a1b8fea62c5a8 (patch)
tree76143602c2733161100d817a58c4cb5b93730c7d
parentdb938913736600ce3ad68d89a9a3532c4cd517f5 (diff)
downloadopencode-d540d363a76909c9c1b1d4e4113a1b8fea62c5a8.tar.gz
opencode-d540d363a76909c9c1b1d4e4113a1b8fea62c5a8.zip
refactor: simplify solid reactivity across app and web (#20497)
-rw-r--r--packages/app/src/pages/session.tsx5
-rw-r--r--packages/ui/src/components/line-comment-annotations.tsx5
-rw-r--r--packages/ui/src/components/line-comment.tsx19
-rw-r--r--packages/ui/src/components/message-part.tsx31
-rw-r--r--packages/ui/src/components/session-turn.tsx5
-rw-r--r--packages/web/src/components/Share.tsx14
-rw-r--r--packages/web/src/components/share/common.tsx11
-rw-r--r--packages/web/src/components/share/content-diff.tsx47
8 files changed, 70 insertions, 67 deletions
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 18bae6e2d..e51161590 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -329,10 +329,9 @@ export default function Page() {
const { params, sessionKey, tabs, view } = useSessionLayout()
createEffect(() => {
- if (!untrack(() => prompt.ready())) return
- prompt.ready()
+ if (!prompt.ready()) return
untrack(() => {
- if (params.id || !prompt.ready()) return
+ if (params.id) return
const text = searchParams.prompt
if (!text) return
prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
diff --git a/packages/ui/src/components/line-comment-annotations.tsx b/packages/ui/src/components/line-comment-annotations.tsx
index 80018d3dd..f0286a36a 100644
--- a/packages/ui/src/components/line-comment-annotations.tsx
+++ b/packages/ui/src/components/line-comment-annotations.tsx
@@ -294,11 +294,6 @@ export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
cancelDraft()
}
- createEffect(() => {
- props.commenting()
- setDraft("")
- })
-
return {
draft,
setDraft,
diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx
index f0e29a485..26e763bb3 100644
--- a/packages/ui/src/components/line-comment.tsx
+++ b/packages/ui/src/components/line-comment.tsx
@@ -1,6 +1,6 @@
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { createEffect, createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js"
+import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js"
import { Button } from "./button"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
@@ -210,7 +210,6 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
const refs = {
textarea: undefined as HTMLTextAreaElement | undefined,
}
- const [text, setText] = createSignal(split.value)
const [open, setOpen] = createSignal(false)
function selectMention(item: { path: string } | undefined) {
@@ -220,10 +219,9 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
const query = currentMention()
if (!textarea || !query) return
- const value = `${text().slice(0, query.start)}@${item.path} ${text().slice(query.end)}`
+ const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
const cursor = query.start + item.path.length + 2
- setText(value)
split.onInput(value)
closeMention()
@@ -257,10 +255,6 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
fn()
}
- createEffect(() => {
- setText(split.value)
- })
-
const closeMention = () => {
setOpen(false)
mention.clear()
@@ -302,7 +296,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
}
const submit = () => {
- const value = text().trim()
+ const value = split.value.trim()
if (!value) return
split.onSubmit(value)
}
@@ -322,10 +316,9 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
data-slot="line-comment-textarea"
rows={split.rows ?? 3}
placeholder={split.placeholder ?? i18n.t("ui.lineComment.placeholder")}
- value={text()}
+ value={split.value}
on:input={(e) => {
const value = (e.currentTarget as HTMLTextAreaElement).value
- setText(value)
split.onInput(value)
syncMention()
}}
@@ -422,7 +415,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
type="button"
data-slot="line-comment-action"
data-variant="primary"
- disabled={text().trim().length === 0}
+ disabled={split.value.trim().length === 0}
on:mousedown={hold as any}
on:click={click(submit) as any}
>
@@ -434,7 +427,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => {
<Button size="small" variant="ghost" onClick={split.onCancel}>
{split.cancelLabel ?? i18n.t("ui.common.cancel")}
</Button>
- <Button size="small" variant="primary" disabled={text().trim().length === 0} onClick={submit}>
+ <Button size="small" variant="primary" disabled={split.value.trim().length === 0} onClick={submit}>
{split.submitLabel ?? i18n.t("ui.lineComment.submit")}
</Button>
</Show>
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 1555a09a0..03477e5a7 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -230,6 +230,19 @@ function createPacedValue(getValue: () => string, live?: () => boolean) {
return value
}
+function PacedMarkdown(props: { text: string; cacheKey: string; streaming: boolean }) {
+ const value = createPacedValue(
+ () => props.text,
+ () => props.streaming,
+ )
+
+ return (
+ <Show when={value()}>
+ <Markdown text={value()} cacheKey={props.cacheKey} streaming={props.streaming} />
+ </Show>
+ )
+}
+
function relativizeProjectPath(path: string, directory?: string) {
if (!path) return ""
if (!directory) return path
@@ -1373,8 +1386,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
const streaming = createMemo(
() => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number",
)
- const displayText = () => (part().text ?? "").trim()
- const throttledText = createPacedValue(displayText, streaming)
+ const text = () => (part().text ?? "").trim()
const isLastTextPart = createMemo(() => {
const last = (data.store.part?.[props.message.id] ?? [])
.filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim())
@@ -1390,7 +1402,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
const [copied, setCopied] = createSignal(false)
const handleCopy = async () => {
- const content = displayText()
+ const content = text()
if (!content) return
await navigator.clipboard.writeText(content)
setCopied(true)
@@ -1398,10 +1410,12 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
}
return (
- <Show when={throttledText()}>
+ <Show when={text()}>
<div data-component="text-part">
<div data-slot="text-part-body">
- <Markdown text={throttledText()} cacheKey={part().id} streaming={streaming()} />
+ <Show when={streaming()} fallback={<Markdown text={text()} cacheKey={part().id} streaming={false} />}>
+ <PacedMarkdown text={text()} cacheKey={part().id} streaming={streaming()} />
+ </Show>
</div>
<Show when={showCopy()}>
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
@@ -1437,12 +1451,13 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) {
() => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number",
)
const text = () => part().text.trim()
- const throttledText = createPacedValue(text, streaming)
return (
- <Show when={throttledText()}>
+ <Show when={text()}>
<div data-component="reasoning-part">
- <Markdown text={throttledText()} cacheKey={part().id} streaming={streaming()} />
+ <Show when={streaming()} fallback={<Markdown text={text()} cacheKey={part().id} streaming={false} />}>
+ <PacedMarkdown text={text()} cacheKey={part().id} streaming={streaming()} />
+ </Show>
</div>
</Show>
)
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index ed4c0e914..fe029485a 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -343,14 +343,12 @@ export function SessionTurn(
})
const assistantDerived = createMemo(() => {
let visible = 0
- let tail: "text" | "other" | undefined
let reason: string | undefined
const show = showReasoningSummaries()
for (const message of assistantMessages()) {
for (const part of list(data.store.part?.[message.id], emptyParts)) {
if (partState(part, show) === "visible") {
visible++
- tail = part.type === "text" ? "text" : "other"
}
if (part.type === "reasoning" && part.text) {
const h = heading(part.text)
@@ -358,10 +356,9 @@ export function SessionTurn(
}
}
}
- return { visible, tail, reason }
+ return { visible, reason }
})
const assistantVisible = createMemo(() => assistantDerived().visible)
- const assistantTailVisible = createMemo(() => assistantDerived().tail)
const reasoningHeading = createMemo(() => assistantDerived().reason)
const showThinking = createMemo(() => {
if (!working() || !!error()) return false
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index de12baede..3ee86c270 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -366,21 +366,13 @@ export default function Share(props: {
<Suspense>
<For each={filteredParts()}>
{(part, partIndex) => {
- const last = createMemo(
- () =>
- data().messages.length === msgIndex() + 1 &&
- filteredParts().length === partIndex() + 1,
- )
+ const last = () =>
+ data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1
onMount(() => {
const hash = window.location.hash.slice(1)
// Wait till all parts are loaded
- if (
- hash !== "" &&
- !hasScrolledToAnchor &&
- filteredParts().length === partIndex() + 1 &&
- data().messages.length === msgIndex() + 1
- ) {
+ if (hash !== "" && !hasScrolledToAnchor && last()) {
hasScrolledToAnchor = true
scrollToAnchor(hash)
}
diff --git a/packages/web/src/components/share/common.tsx b/packages/web/src/components/share/common.tsx
index 7ca4daa6a..aebc95537 100644
--- a/packages/web/src/components/share/common.tsx
+++ b/packages/web/src/components/share/common.tsx
@@ -83,12 +83,15 @@ export function createOverflow() {
return overflow()
},
ref(el: HTMLElement) {
+ const sync = () => {
+ setOverflow(el.scrollHeight > el.clientHeight + 1)
+ }
+
const ro = new ResizeObserver(() => {
- if (el.scrollHeight > el.clientHeight + 1) {
- setOverflow(true)
- }
- return
+ sync()
})
+
+ sync()
ro.observe(el)
onCleanup(() => {
diff --git a/packages/web/src/components/share/content-diff.tsx b/packages/web/src/components/share/content-diff.tsx
index 9ccd554d0..c8dd35b38 100644
--- a/packages/web/src/components/share/content-diff.tsx
+++ b/packages/web/src/components/share/content-diff.tsx
@@ -1,5 +1,5 @@
import { parsePatch } from "diff"
-import { createMemo } from "solid-js"
+import { createMemo, For } from "solid-js"
import { ContentCode } from "./content-code"
import styles from "./content-diff.module.css"
@@ -160,28 +160,37 @@ export function ContentDiff(props: Props) {
return (
<div class={styles.root}>
<div data-component="desktop">
- {rows().map((r) => (
- <div data-component="diff-row" data-type={r.type}>
- <div data-slot="before" data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}>
- <ContentCode code={r.left} flush lang={props.lang} />
- </div>
- <div data-slot="after" data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}>
- <ContentCode code={r.right} lang={props.lang} flush />
+ <For each={rows()}>
+ {(row) => (
+ <div data-component="diff-row" data-type={row.type}>
+ <div
+ data-slot="before"
+ data-diff-type={row.type === "removed" || row.type === "modified" ? "removed" : ""}
+ >
+ <ContentCode code={row.left} flush lang={props.lang} />
+ </div>
+ <div data-slot="after" data-diff-type={row.type === "added" || row.type === "modified" ? "added" : ""}>
+ <ContentCode code={row.right} lang={props.lang} flush />
+ </div>
</div>
- </div>
- ))}
+ )}
+ </For>
</div>
<div data-component="mobile">
- {mobileRows().map((block) => (
- <div data-component="diff-block" data-type={block.type}>
- {block.lines.map((line) => (
- <div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>
- <ContentCode code={line} lang={props.lang} flush />
- </div>
- ))}
- </div>
- ))}
+ <For each={mobileRows()}>
+ {(block) => (
+ <div data-component="diff-block" data-type={block.type}>
+ <For each={block.lines}>
+ {(line) => (
+ <div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>
+ <ContentCode code={line} lang={props.lang} flush />
+ </div>
+ )}
+ </For>
+ </div>
+ )}
+ </For>
</div>
</div>
)