Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
09e2e25
feat(opencode): support OpenCode 2.0 preview
maria-rcks Aug 19, 2026
0de9cb1
fix(opencode): harden OpenCode 2 ACP lifecycle
maria-rcks Aug 19, 2026
a607feb
fix(opencode): avoid retaining preview secrets
maria-rcks Aug 19, 2026
09056c5
fix(opencode): handle ACP form edge cases
maria-rcks Aug 19, 2026
5dbcea0
fix(ui): clarify free-form provider questions
maria-rcks Aug 19, 2026
6a56d3e
fix(opencode): close ACP recovery edge cases
maria-rcks Aug 19, 2026
4589348
fix(opencode): preserve ACP recovery choices
maria-rcks Aug 19, 2026
604f130
fix(opencode): preserve ACP reload state
maria-rcks Aug 19, 2026
b8bbdcb
fix(opencode): retain configured ACP model
maria-rcks Aug 19, 2026
6325bbc
fix(opencode): clean up interrupted ACP turns
maria-rcks Aug 19, 2026
ee8767c
test(opencode): await ACP reload receipt
maria-rcks Aug 19, 2026
cfe3b1c
fix(opencode): serialize ACP configuration
maria-rcks Aug 19, 2026
5c0cef7
fix(opencode): resolve queued ACP model late
maria-rcks Aug 19, 2026
2e144ca
fix(opencode): drain ACP events before turn completion
maria-rcks Aug 19, 2026
aa18375
fix(opencode): serialize session stop with turn settlement
maria-rcks Aug 19, 2026
262cfd1
fix(opencode): complete session teardown atomically
maria-rcks Aug 19, 2026
d4ed422
fix(opencode): type ACP reload failures
maria-rcks Aug 19, 2026
367aa35
fix(opencode): preserve ACP reload error context
maria-rcks Aug 19, 2026
836c109
fix(opencode): abort turns before reload teardown
maria-rcks Aug 19, 2026
5a42365
fix(web): clarify OpenCode free-form prompts
maria-rcks Aug 19, 2026
d43f0f1
fix(opencode): return free-form ACP answers
maria-rcks Aug 19, 2026
6f8e312
fix(opencode): finish reload teardown atomically
maria-rcks Aug 19, 2026
88ae421
fix(opencode): serialize session lifecycle commits
maria-rcks Aug 19, 2026
7b9bff9
Merge remote-tracking branch 'origin/main' into feat/opencode2-support
maria-rcks Aug 21, 2026
b5e4dba
fix(web): restore NUL separators in composer stash dedupe keys
maria-rcks Aug 21, 2026
809355e
feat(opencode): replace opencode2 ACP bridge with native server attach
maria-rcks Aug 21, 2026
efaca07
refactor(opencode): simplify v2 discovery wiring
maria-rcks Aug 21, 2026
591181a
fix(opencode): fall back to the stable binary when binaryPath is empty
maria-rcks Aug 21, 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
89 changes: 46 additions & 43 deletions apps/mobile/src/features/threads/PendingUserInputCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) {
>
{props.pendingUserInput.questions.map((question) => {
const draft = props.drafts[question.id];
const hasOptions = question.options.length > 0;
return (
<View key={question.id} className="gap-2 pt-1">
<Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500 dark:text-neutral-500">
Expand All @@ -257,57 +258,59 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) {
<Text className="font-sans text-base leading-snug text-neutral-950 dark:text-neutral-50">
{question.question}
</Text>
<View className="gap-2">
{question.options.map((option) => {
const selected = isPendingUserInputOptionSelected(draft, option.label);
const description =
option.description !== option.label ? option.description : undefined;
return (
<Pressable
key={option.label}
className={cn(
"min-h-12 w-full rounded-2xl border px-3.5 py-3",
selected
? "border-blue-300/50 bg-blue-50 dark:border-blue-400/28 dark:bg-blue-400/14"
: "border-neutral-200 bg-white dark:border-white/6 dark:bg-neutral-950/70",
)}
onPress={() =>
props.onSelectOption(
props.pendingUserInput.requestId,
question,
option.label,
)
}
>
<View className="min-w-0 flex-1 gap-0.5">
<Text
className={cn(
"font-t3-bold text-sm",
selected
? "text-sky-700 dark:text-sky-300"
: "text-neutral-700 dark:text-neutral-200",
)}
>
{option.label}
</Text>
{description ? (
<Text className="font-sans text-sm leading-5 text-neutral-500 dark:text-neutral-400">
{description}
{hasOptions ? (
<View className="gap-2">
{question.options.map((option) => {
const selected = isPendingUserInputOptionSelected(draft, option.label);
const description =
option.description !== option.label ? option.description : undefined;
return (
<Pressable
key={option.label}
className={cn(
"min-h-12 w-full rounded-2xl border px-3.5 py-3",
selected
? "border-blue-300/50 bg-blue-50 dark:border-blue-400/28 dark:bg-blue-400/14"
: "border-neutral-200 bg-white dark:border-white/6 dark:bg-neutral-950/70",
)}
onPress={() =>
props.onSelectOption(
props.pendingUserInput.requestId,
question,
option.label,
)
}
>
<View className="min-w-0 flex-1 gap-0.5">
<Text
className={cn(
"font-t3-bold text-sm",
selected
? "text-sky-700 dark:text-sky-300"
: "text-neutral-700 dark:text-neutral-200",
)}
>
{option.label}
</Text>
) : null}
</View>
</Pressable>
);
})}
</View>
{description ? (
<Text className="font-sans text-sm leading-5 text-neutral-500 dark:text-neutral-400">
{description}
</Text>
) : null}
</View>
</Pressable>
);
})}
</View>
) : null}
<TextInput
value={draft?.customAnswer ?? ""}
onChangeText={(value) =>
props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value)
}
onFocus={() => props.onInputFocusChange?.(true)}
onBlur={() => props.onInputFocusChange?.(false)}
placeholder="Or type a custom answer"
placeholder={hasOptions ? "Or type a custom answer" : "Type your answer"}
className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-base text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50"
/>
</View>
Expand Down
25 changes: 25 additions & 0 deletions apps/mobile/src/lib/threadActivity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {
buildPendingUserInputAnswers,
buildThreadFeed,
derivePendingUserInputs,
deriveThreadFeedPresentation,
isPendingUserInputOptionSelected,
setPendingUserInputCustomAnswer,
Expand Down Expand Up @@ -45,6 +46,30 @@ const multiSelectQuestion = {
} as const;

describe("pending user input answers", () => {
it("keeps free-form questions with no preset options", () => {
const pending = derivePendingUserInputs([
makeActivity({
id: EventId.make("user-input-free-form"),
kind: "user-input.requested",
summary: "User input requested",
createdAt: "2026-04-01T00:00:00.000Z",
payload: {
requestId: "req-user-input-free-form",
questions: [
{
id: "name",
header: "Name",
question: "What should this be called?",
options: [],
},
],
},
}),
]);

expect(pending[0]?.questions[0]?.options).toEqual([]);
});

it("replaces single-select options and toggles multi-select options", () => {
expect(
togglePendingUserInputOptionSelection(
Expand Down
3 changes: 0 additions & 3 deletions apps/mobile/src/lib/threadActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,6 @@ function parseUserInputQuestions(
};
})
.filter((option): option is UserInputQuestion["options"][number] => option !== null);
if (options.length === 0) {
return null;
}
return {
id: question.id,
header: question.header,
Expand Down
Loading
Loading