Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
904f862
feat(i18n): Add RTL (Right-to-Left) support for Arabic and other RTL …
Feb 19, 2026
7d17167
feat(i18n): Add RTL (Right-to-Left) support for Arabic and other RTL …
Feb 19, 2026
4ce3213
Merge branch 'feature/rtl-2' into feature/rtl-detector
Feb 19, 2026
069eae7
fix: isRtlFunction
Feb 19, 2026
ab8601e
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 19, 2026
1318559
Merge branch 'dev' into feature/rtl-detector
Feb 19, 2026
79af063
fix: Cannot find module '@aws-sdk/credential-providers'
Feb 19, 2026
b2ab9bc
Merge branch 'feature/rtl-detector' of https://github.com/alefbt/open…
Feb 19, 2026
18248a2
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 19, 2026
a434187
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 19, 2026
b3bd30c
fix: remove bun.lock
Feb 19, 2026
040782d
fix
Feb 19, 2026
48e66d7
Merge branch 'feature/rtl-detector' of https://github.com/alefbt/open…
Feb 19, 2026
bbe6cbe
Merge branch 'anomalyco:dev' into feature/rtl-detector
alefbt Feb 19, 2026
ff2074b
fix
Feb 19, 2026
12a89f7
Merge branch 'feature/rtl-detector' of https://github.com/alefbt/open…
Feb 19, 2026
b743f0d
fix
Feb 19, 2026
6a428ab
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 19, 2026
973715f
anthropic legal requests
thdxr Feb 19, 2026
6f0ce9d
chore: generate
opencode-agent[bot] Feb 19, 2026
8759bf3
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 19, 2026
9a4b95f
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 22, 2026
42925c8
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 22, 2026
1528215
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 23, 2026
0db02bb
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 23, 2026
8e76b81
fix
Feb 23, 2026
1b3d81c
marge from dev to undo over all changes
Feb 23, 2026
0fd408c
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 23, 2026
87f2b30
fix
Feb 23, 2026
ab66a05
Merge branch 'feature/rtl-detector' of https://github.com/alefbt/open…
Feb 23, 2026
e709d65
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 24, 2026
d7a27fa
Merge branch 'anomalyco:dev' into feature/rtl-detector
alefbt Feb 24, 2026
b82a7c9
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 24, 2026
c86fc7f
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 24, 2026
d60db05
Merge branch 'dev' into feature/rtl-detector
alefbt Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments"
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
import { promptPlaceholder } from "./prompt-input/placeholder"
import { ImagePreview } from "@opencode-ai/ui/image-preview"
import { isRtlText } from "@opencode-ai/ui/message-part"

interface PromptInputProps {
class?: string
Expand Down Expand Up @@ -406,6 +407,24 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const [composing, setComposing] = createSignal(false)
const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229

const [isRTL, setIsRTL] = createSignal(false)
createEffect(() => {
const text = prompt
.current()
.map((part) => ("content" in part ? part.content : ""))
.join("")
setIsRTL(isRtlText(text))
})

createEffect(() => {
if (!isFocused()) closePopover()
})

// Safety: reset composing state on focus change to prevent stuck state
// This handles edge cases where compositionend event may not fire
createEffect(() => {
if (!isFocused()) setComposing(false)
})
const handleBlur = () => {
closePopover()
setComposing(false)
Expand Down Expand Up @@ -1135,12 +1154,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onCompositionEnd={() => setComposing(false)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
dir={isRTL() ? "rtl" : "ltr"}
classList={{
"select-text": true,
"w-full pl-3 pr-2 pt-2 pb-11 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"[&_[data-type=file]]:text-syntax-property": true,
"[&_[data-type=agent]]:text-syntax-type": true,
"font-mono!": store.mode === "shell",
"text-right": isRTL(),
}}
/>
<Show when={!prompt.dirty()}>
Expand Down
32 changes: 30 additions & 2 deletions packages/ui/src/components/message-part.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ function getDiagnostics(
return diagnostics.filter((d) => d.severity === 1).slice(0, 3)
}

export const isRtlText = (text: string) => {
const rtlChars = /[\u0590-\u05FF\u0600-\u06FF]/
let rtlCount = 0
let totalCount = 0

for (const char of text) {
if (char.trim() !== "") {
totalCount++
if (rtlChars.test(char)) {
rtlCount++

}
}
}

if (totalCount < 3) return false

return rtlCount / totalCount > 0.5
}

function DiagnosticsDisplay(props: { diagnostics: Diagnostic[] }): JSX.Element {
const i18n = useI18n()
return (
Expand Down Expand Up @@ -676,6 +696,10 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
)

const text = createMemo(() => textPart()?.text || "")
const [isRTL, setIsRTL] = createSignal(false)
createEffect(() => {
setIsRTL(isRtlText(text()))
})

const files = createMemo(() => (props.parts?.filter((p) => p.type === "file") as FilePart[]) ?? [])

Expand Down Expand Up @@ -773,7 +797,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
<Show when={text()}>
<>
<div data-slot="user-message-body">
<div data-slot="user-message-text">
<div data-slot="user-message-text" dir={isRTL() ? "rtl" : "ltr"} classList={{ "text-right": isRTL() }}>
<HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
</div>
</div>
Expand Down Expand Up @@ -1146,6 +1170,10 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
})

const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
const [isRTL, setIsRTL] = createSignal(false)
createEffect(() => {
setIsRTL(isRtlText(displayText()))
})
const throttledText = createThrottledValue(displayText)
const isLastTextPart = createMemo(() => {
const last = (data.store.part?.[props.message.id] ?? [])
Expand All @@ -1171,7 +1199,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {

return (
<Show when={throttledText()}>
<div data-component="text-part">
<div data-component="text-part" dir={isRTL() ? "rtl" : "ltr"} classList={{ "text-right": isRTL() }}>
<div data-slot="text-part-body">
<Markdown text={throttledText()} cacheKey={part.id} />
</div>
Expand Down
Loading