Skip to content

Commit c266587

Browse files
committed
fix(ui): eliminate activity tab lag during execution streaming
Root cause: the virtualized activity log list used an unstable React key that included logs.length. Every new tool call log appended increased logs.length by 1, which changed the key of EVERY visible activity item, forcing React to unmount and remount all DOM nodes on every single log entry. During execution this happens 10+ times per second, freezing the entire app on WebKitGTK. Fixes: - Key: use virtualRow.key (stable per index) instead of logs.length - Remove measureElement ref overhead (fixed estimateSize is sufficient) - Tune virtualizer: estimateSize 60, overscan 3 (was 88/8) - Remove scrollTop=0 useEffect that fought the virtualizer - Add will-change:transform hint for compositor efficiency - execution-drawer: change smooth scroll to auto (eliminates continuous animation thrashing while logs stream in)
1 parent 2e86fb9 commit c266587

3 files changed

Lines changed: 66 additions & 12 deletions

File tree

ISSUES.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,67 @@
55
66
---
77

8+
## [2026-05-26] — Fix sidecar startup crash (missing openid-client + otpauth + ALLOW_SHARED_OPENCODE)
9+
10+
**Status:** Done
11+
**Agent:** Sisyphus (OpenCode)
12+
13+
### What was done
14+
- Sidecar was crashing at startup with `ERR_MODULE_NOT_FOUND: Cannot find package 'openid-client'`. Root cause: `apps/api/src/services/sso.ts` imports `openid-client` and `apps/api/src/routes/totp.ts` imports `otpauth` — both declared in `apps/api/package.json` but not installed in `node_modules`.
15+
- Ran `pnpm install` at workspace root to pull the missing packages (`+17` packages installed).
16+
- Sidecar was also immediately exiting after recovery due to multi-user guard (`OPENLINEAR_ALLOW_SHARED_OPENCODE` not set, 4 users in DB). Added `OPENLINEAR_ALLOW_SHARED_OPENCODE=1` to `.env` for the dev machine.
17+
- Verified sidecar now starts cleanly: binds port 3001 + 1455, runs recovery, and stays up.
18+
19+
### Files changed
20+
- `.env` — added `OPENLINEAR_ALLOW_SHARED_OPENCODE=1`
21+
- `pnpm-lock.yaml` — updated by `pnpm install` (openid-client, otpauth, and transitive deps)
22+
23+
### Issues encountered
24+
- None after the fix.
25+
26+
### Next steps / blockers
27+
- None. `pnpm dev-live` / `pnpm start` should now boot the sidecar without crashing.
28+
29+
---
30+
31+
## [2026-05-26] — Model provider onboarding step + execution model-not-configured guard
32+
33+
**Status:** Done
34+
**Agent:** Sisyphus (OpenCode)
35+
36+
### What was done
37+
- Added a 4th onboarding step "AI Model" with Back / Finish setup / Skip buttons. Team creation now advances to the new step instead of completing directly. Both Finish and Skip complete onboarding; Skip shows a toast directing the user to Settings.
38+
- Built `ModelProviderStep` component that shows live provider status fetched from the sidecar, lists connected providers, and links to Settings → AI Providers. Handles sidecar-unavailable gracefully (shows retry + lets user skip).
39+
- Added `pickAutoDetectedModel()` pure helper and `ensureModelConfigured(userId)` service in the sidecar. Auto-detection priority: anthropic → openai → google → openrouter → first connected. When a provider is authenticated but no model is selected, the function picks and persists the provider's default model automatically.
40+
- Updated `GET /api/opencode/setup-status` to run auto-detection and return `hasProvider` + `hasModel`. `ready` is now `hasProvider && hasModel` (previously only `hasProvider`), so it correctly reports false when a provider is connected but no model is selected.
41+
- Added pre-flight `ensureModelConfigured` check to `POST /api/tasks/:id/execute` and `POST /api/batches`. Returns `400 { code: 'MODEL_NOT_CONFIGURED', details: { hasProvider } }` with a human-readable message instead of silently starting a doomed agent.
42+
- Added `isModelNotConfiguredApiError()` helper in `lib/api/model-errors.ts` to detect the new error code on the frontend.
43+
- Updated single-execute path (`use-kanban-board.ts`) and batch-execute path (`use-batch-execution.ts`) to catch `MODEL_NOT_CONFIGURED` — toast the specific message and reopen the provider setup dialog automatically.
44+
- Provider setup dialog now distinguishes "no provider" vs "provider connected but no model selected" in toasts.
45+
46+
### Files changed
47+
- `apps/sidecar/src/services/opencode-model.ts` — new file: `pickAutoDetectedModel`, `ensureModelConfigured`
48+
- `apps/sidecar/src/routes/opencode.ts``setup-status` uses auto-detect, exposes `hasProvider`/`hasModel`, stricter `ready`
49+
- `apps/sidecar/src/routes/execution.ts` — pre-flight model check on single execute
50+
- `apps/sidecar/src/routes/batches.ts` — pre-flight model check on batch create
51+
- `apps/desktop-ui/lib/api/opencode.ts``SetupStatus` extended with `hasProvider`/`hasModel`
52+
- `apps/desktop-ui/lib/api/model-errors.ts` — new file: `isModelNotConfiguredApiError`, `readModelNotConfiguredDetails`
53+
- `apps/desktop-ui/components/board/use-kanban-board.ts` — MODEL_NOT_CONFIGURED handling in single execute + retry path
54+
- `apps/desktop-ui/hooks/use-batch-execution.ts` — MODEL_NOT_CONFIGURED handling in batch execute; improved provider/model-specific toasts
55+
- `apps/desktop-ui/components/onboarding/steps/onboarding-utils.ts` — added "AI Model" to STEP_LABELS
56+
- `apps/desktop-ui/components/onboarding/steps/model-provider-step.tsx` — new file: onboarding step 3 UI
57+
- `apps/desktop-ui/components/onboarding/onboarding-wizard.tsx` — wired step 3, `handleFinishOnboarding`, `handleSkipOnboarding`, step clamp bumped to 3
58+
59+
### Issues encountered
60+
- Pre-existing unrelated typecheck failures in `apps/api/` (`totp.ts`, `invitations.ts`, `sso.ts`, `tasks.ts`) for half-built features missing Prisma models and npm packages. Left untouched per protocol.
61+
62+
### Next steps / blockers
63+
- Manually verify: complete onboarding on a fresh workspace → should land on AI Model step → Finish or Skip both enter the app.
64+
- Manually verify: try executing a task/batch with no model configured → should get clear toast + provider dialog opens.
65+
- Consider adding a "model configured" status indicator somewhere on the board (e.g., a subtle warning badge on the Execute buttons when `!status.ready`).
66+
67+
---
68+
869
## [2026-05-24] — Add Electron desktop wrapper for Linux (fixing WebKitGTK scroll/font issues)
970

1071
**Status:** Done

apps/desktop-ui/components/execution-drawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export function ExecutionDrawer({ task, logs, progress, open, onClose }: Executi
8181

8282
useEffect(() => {
8383
if (logsEndRef.current && open) {
84-
logsEndRef.current.scrollIntoView({ behavior: 'smooth' })
84+
logsEndRef.current.scrollIntoView({ behavior: 'auto' })
8585
}
8686
}, [logs, open])
8787

apps/desktop-ui/components/task-detail-view.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ export function TaskDetailView({ task, progress, open, onClose, onDelete, deleti
9595
const logVirtualizer = useVirtualizer({
9696
count: reversedLogs.length,
9797
getScrollElement: () => logsContainerRef.current,
98-
estimateSize: () => 88,
99-
overscan: 8,
98+
estimateSize: () => 60,
99+
overscan: 3,
100100
})
101101
const [editingTitle, setEditingTitle] = useState(false)
102102
const [editingDescription, setEditingDescription] = useState(false)
@@ -107,12 +107,6 @@ export function TaskDetailView({ task, progress, open, onClose, onDelete, deleti
107107
const titleSavedRef = useRef(false)
108108
const descriptionSavedRef = useRef(false)
109109

110-
useEffect(() => {
111-
if (logsContainerRef.current && open && logs.length > 0) {
112-
logsContainerRef.current.scrollTop = 0
113-
}
114-
}, [logs, open])
115-
116110
useEffect(() => {
117111
if (!detailIsExecuting) setCancelling(false)
118112
}, [detailIsExecuting])
@@ -427,11 +421,10 @@ export function TaskDetailView({ task, progress, open, onClose, onDelete, deleti
427421

428422
return (
429423
<div
430-
key={`${log.timestamp}-${logs.length - virtualRow.index}`}
424+
key={virtualRow.key}
431425
data-index={virtualRow.index}
432-
ref={logVirtualizer.measureElement}
433426
className="absolute left-0 top-0 flex w-full gap-3"
434-
style={{ transform: `translateY(${virtualRow.start}px)` }}
427+
style={{ transform: `translateY(${virtualRow.start}px)`, willChange: 'transform' }}
435428
>
436429
<div className="flex flex-col items-center flex-shrink-0 w-6">
437430
<div className={cn(

0 commit comments

Comments
 (0)