diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..d10e7b1c8c8f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "references/codex"] + path = references/codex + url = https://github.com/openai/codex +[submodule "references/cline"] + path = references/cline + url = https://github.com/cline/cline +[submodule "references/awesome-opencode"] + path = references/awesome-opencode + url = https://github.com/awesome-opencode/awesome-opencode +[submodule "references/OpenGUI"] + path = references/OpenGUI + url = https://github.com/akemmanuel/OpenGUI +[submodule "references/AionUi"] + path = references/AionUi + url = https://github.com/iOfficeAI/AionUi diff --git a/AI_REVIEW.md b/AI_REVIEW.md new file mode 100644 index 000000000000..bc3462933800 --- /dev/null +++ b/AI_REVIEW.md @@ -0,0 +1,1428 @@ +# 🔍 Code Review - 2/26/2026, 4:03:24 PM + +**Project:** AI Visual Code Review +**Generated by:** AI Visual Code Review v2.0 + +## 📊 Change Summary + +``` +docs/09-temp/codex-queue-steer-architecture.md | 319 +++++++++++++++++++++ + packages/app/src/components/prompt-input.tsx | 256 ++++++++++++++--- + .../app/src/context/global-sync/child-store.ts | 1 + + .../src/context/global-sync/event-reducer.test.ts | 1 + + .../app/src/context/global-sync/event-reducer.ts | 9 +- + packages/app/src/context/global-sync/types.ts | 3 + + packages/opencode/src/server/routes/session.ts | 117 ++++++++ + packages/opencode/src/session/prompt.ts | 51 ++++ + packages/opencode/src/session/steer.ts | 127 ++++++++ + packages/opencode/test/session/steer.test.ts | 235 +++++++++++++++ + 10 files changed, 1084 insertions(+), 35 deletions(-) +``` + +## 📝 Files Changed (10 selected) + + +### ✨ `docs/09-temp/codex-queue-steer-architecture.md` **[ADDED]** + +**Status:** ✅ **NEW FILE** - This file has been newly created + +**Type:** Documentation 📖 + +```diff +@@ -0,0 +1,319 @@ + 1 +# Codex Queue/Steer Architecture Analysis + 2 + + 3 +> Deep-dive into OpenAI Codex CLI's queue/steer mechanism for mid-turn user interaction. + 4 +> Source: `references/codex/` submodule + 5 + + 6 +--- + 7 + + 8 +## Overview + 9 + + 10 +Codex implements a **dual-input model** that lets users interact with the agent **during** an active turn, not just between turns: + 11 + + 12 +| Action | Keybinding | Behavior | When Turn Active | + 13 +|--------|-----------|----------|-----------------| + 14 +| **Queue** | `Enter` | Enqueue message for next turn boundary | Message waits in queue, displayed in UI | + 15 +| **Steer** | `⌘Enter` / `Enter` (steer-mode) | Inject input into active turn immediately | Message sent to model in current context | + 16 + + 17 +--- + 18 + + 19 +## Architecture Layers + 20 + + 21 +``` + 22 +┌─────────────────────────────────────────────────┐ + 23 +│ TUI Layer (tui/src/) │ + 24 +│ ┌─────────────────────────────────────────┐ │ + 25 +│ │ ChatComposer │ │ + 26 +│ │ Enter → InputResult::Submitted (steer) │ │ + 27 +│ │ Tab → InputResult::Queued │ │ + 28 +│ └─────────────────┬───────────────────────┘ │ + 29 +│ │ │ + 30 +│ ┌─────────────────▼───────────────────────┐ │ + 31 +│ │ QueuedUserMessages widget │ │ + 32 +│ │ Shows queued messages with "↳" prefix │ │ + 33 +│ │ Alt+Up to pop back into composer │ │ + 34 +│ └─────────────────────────────────────────┘ │ + 35 +└────────────────────┬────────────────────────────┘ + 36 + │ + 37 +┌────────────────────▼────────────────────────────┐ + 38 +│ App Server Protocol (app-server-protocol/) │ + 39 +│ │ + 40 +│ turn/start → TurnStartParams (new turn) │ + 41 +│ turn/steer → TurnSteerParams (mid-turn) │ + 42 +│ │ + 43 +│ TurnSteerParams { │ + 44 +│ thread_id: String, │ + 45 +│ input: Vec, │ + 46 +│ expected_turn_id: String, // guard │ + 47 +│ } │ + 48 +│ │ + 49 +│ TurnSteerResponse { │ + 50 +│ turn_id: String, // confirms active turn │ + 51 +│ } │ + 52 +└────────────────────┬────────────────────────────┘ + 53 + │ + 54 +┌────────────────────▼────────────────────────────┐ + 55 +│ App Server (app-server/src/) │ + 56 +│ codex_message_processor.rs │ + 57 +│ │ + 58 +│ async fn turn_steer(&self, req_id, params) { │ + 59 +│ let thread = load_thread(params.thread_id); │ + 60 +│ thread.steer_input( │ + 61 +│ mapped_items, │ + 62 +│ Some(¶ms.expected_turn_id) │ + 63 +│ ); │ + 64 +│ // Returns turn_id or error: │ + 65 +│ // "no active turn to steer" │ + 66 +│ } │ + 67 +└────────────────────┬────────────────────────────┘ + 68 + │ + 69 +┌────────────────────▼────────────────────────────┐ + 70 +│ Core Engine (core/src/codex.rs) │ + 71 +│ │ + 72 +│ Session::steer_input(input, expected_turn_id) │ + 73 +│ 1. Validate input not empty │ + 74 +│ 2. Lock active_turn mutex │ + 75 +│ 3. Verify active turn exists │ + 76 +│ 4. Check expected_turn_id matches │ + 77 +│ 5. Lock turn_state │ + 78 +│ 6. push_pending_input(input) ← KEY STEP │ + 79 +│ 7. Return active turn_id │ + 80 +└────────────────────┬────────────────────────────┘ + 81 + │ + 82 +┌────────────────────▼────────────────────────────┐ + 83 +│ Turn State (core/src/state/turn.rs) │ + 84 +│ │ + 85 +│ struct TurnState { │ + 86 +│ pending_input: Vec, │ + 87 +│ } │ + 88 +│ │ + 89 +│ push_pending_input(item) → appends to vec │ + 90 +│ take_pending_input() → drains vec │ + 91 +│ has_pending_input() → checks non-empty │ + 92 +└────────────────────┬────────────────────────────┘ + 93 + │ + 94 +┌────────────────────▼────────────────────────────┐ + 95 +│ Task Loop (core/src/codex.rs ~L4970) │ + 96 +│ │ + 97 +│ loop { │ + 98 +│ // At each iteration, drain pending input │ + 99 +│ let pending = sess.get_pending_input().await; │ + 100 +│ if !pending.is_empty() { │ + 101 +│ // Record as conversation items │ + 102 +│ // → injected into model context │ + 103 +│ for item in pending { │ + 104 +│ record_user_prompt_and_emit_turn_item(); │ + 105 +│ } │ + 106 +│ } │ + 107 +│ // ... send to model, process response ... │ + 108 +│ // On ResponseEvent::Completed: │ + 109 +│ needs_follow_up |= has_pending_input(); │ + 110 +│ // If follow_up needed → loop continues │ + 111 +│ } │ + 112 +└─────────────────────────────────────────────────┘ + 113 +``` + 114 + + 115 +--- + 116 + + 117 +## Core Mechanism: `steer_input` + 118 + + 119 +The heart of steer is `Session::steer_input()` in `core/src/codex.rs`: + 120 + + 121 +```rust + 122 +pub async fn steer_input( + 123 + &self, + 124 + input: Vec, + 125 + expected_turn_id: Option<&str>, + 126 +) -> Result { + 127 + if input.is_empty() { + 128 + return Err(SteerInputError::EmptyInput); + 129 + } + 130 + let mut active = self.active_turn.lock().await; + 131 + let Some(active_turn) = active.as_mut() else { + 132 + return Err(SteerInputError::NoActiveTurn(input)); + 133 + }; + 134 + let Some((active_turn_id, _)) = active_turn.tasks.first() else { + 135 + return Err(SteerInputError::NoActiveTurn(input)); + 136 + }; + 137 + if let Some(expected_turn_id) = expected_turn_id + 138 + && expected_turn_id != active_turn_id + 139 + { + 140 + return Err(SteerInputError::ExpectedTurnMismatch { + 141 + expected: expected_turn_id.to_string(), + 142 + actual: active_turn_id.clone(), + 143 + }); + 144 + } + 145 + let mut turn_state = active_turn.turn_state.lock().await; + 146 + turn_state.push_pending_input(input.into()); + 147 + Ok(active_turn_id.clone()) + 148 +} + 149 +``` + 150 + + 151 +### Key Design Decisions + 152 + + 153 +1. **Non-blocking injection**: `steer_input` just pushes to a `Vec` — it doesn't interrupt or cancel the model. The model's active response completes naturally. + 154 + + 155 +2. **Consumption at loop boundary**: The task loop checks `pending_input` at the **top of each iteration**. After the model finishes a response, if pending input exists, it gets recorded as conversation items and the model is called again with the updated context. + 156 + + 157 +3. **`needs_follow_up` flag**: When a model response completes (`ResponseEvent::Completed`), if there's pending input, the loop sets `needs_follow_up = true` and continues instead of ending the turn. + 158 + + 159 +4. **Turn ID validation**: The `expected_turn_id` field prevents race conditions — the steer request fails if the turn has changed between the user pressing Enter and the server processing the request. + 160 + + 161 +--- + 162 + + 163 +## Error Types + 164 + + 165 +```rust + 166 +pub enum SteerInputError { + 167 + NoActiveTurn(Vec), // No model turn running + 168 + ExpectedTurnMismatch { // Turn changed since request + 169 + expected: String, + 170 + actual: String, + 171 + }, + 172 + EmptyInput, // Nothing to inject + 173 +} + 174 +``` + 175 + + 176 +When `NoActiveTurn` occurs, the app-server falls back — the input that failed to steer gets queued for the next `turn/start`. + 177 + + 178 +--- + 179 + + 180 +## Queue vs Steer: Detailed Comparison + 181 + + 182 +### Queue (Tab / Enter in legacy mode) + 183 + + 184 +1. User types message, presses Tab (or Enter in non-steer mode) + 185 +2. TUI returns `InputResult::Queued { text, text_elements }` + 186 +3. Message stored in `QueuedUserMessages.messages: Vec` + 187 +4. Rendered in UI with `↳` prefix, dimmed/italic + 188 +5. User can pop with Alt+Up to edit + 189 +6. When current turn completes → queued messages become the next `turn/start` + 190 + + 191 +### Steer (Enter in steer mode / ⌘Enter) + 192 + + 193 +1. User types message, presses Enter + 194 +2. TUI returns `InputResult::Submitted { text, text_elements }` + 195 +3. App sends `turn/steer` RPC to server + 196 +4. Server calls `thread.steer_input()` → pushes to `pending_input` + 197 +5. Model's current response continues to completion + 198 +6. At next task loop iteration, pending input is drained and recorded + 199 +7. Model sees the user's steer message in context → generates follow-up + 200 +8. **All within the same turn** — no new turn boundary + 201 + + 202 +### Critical Difference + 203 + + 204 +| Aspect | Queue | Steer | + 205 +|--------|-------|-------| + 206 +| **Timing** | After turn ends | During active turn | + 207 +| **Turn boundary** | Creates new turn | Same turn continues | + 208 +| **Model sees it** | On next turn start | At next loop iteration | + 209 +| **Cancels response** | No (waits) | No (appends to context) | + 210 +| **UI display** | Queued messages widget | Injected into chat transcript | + 211 +| **Fallback** | N/A | Falls back to queue if no active turn | + 212 + + 213 +--- + 214 + + 215 +## Turn Lifecycle with Steer + 216 + + 217 +``` + 218 +Turn Start (user submits prompt) + 219 + │ + 220 + ├─→ Model generates response... + 221 + │ │ + 222 + │ │ ← User presses Enter (steer) + 223 + │ │ → steer_input() pushes to pending_input + 224 + │ │ + 225 + │ ▼ + 226 + │ Response completes + 227 + │ │ + 228 + │ ├─→ has_pending_input()? YES + 229 + │ │ → needs_follow_up = true + 230 + │ │ + 231 + │ ▼ + 232 + │ Loop continues → drain pending_input + 233 + │ → Record steered message as conversation item + 234 + │ → Model sees: [original prompt, response, steered message] + 235 + │ → Model generates new response with full context + 236 + │ │ + 237 + │ ├─→ has_pending_input()? NO + 238 + │ │ → needs_follow_up = false + 239 + │ ▼ + 240 + │ Turn Complete + 241 + │ + 242 + └─→ Queued messages (if any) → next turn/start + 243 +``` + 244 + + 245 +--- + 246 + + 247 +## Turn Completion & Leftover Input + 248 + + 249 +When a task finishes (`task_finished()` in `core/src/tasks/mod.rs`): + 250 + + 251 +```rust + 252 +// 1. Lock active turn + 253 +let mut active = self.active_turn.lock().await; + 254 +// 2. Take any remaining pending input + 255 +let pending_input = ts.take_pending_input(); + 256 +// 3. Clear active turn + 257 +*active = None; + 258 +// 4. Record leftover input as conversation items + 259 +if !pending_input.is_empty() { + 260 + record_conversation_items(&turn_context, &pending_response_items); + 261 +} + 262 +// 5. Emit TurnComplete event + 263 +``` + 264 + + 265 +This ensures steered input is **never lost** — even if the turn ends before the pending input could be consumed by the model loop. + 266 + + 267 +--- + 268 + + 269 +## Feature Flag: `steer_enabled` + 270 + + 271 +Steer is gated behind `Feature::Steer` in the TUI: + 272 + + 273 +```rust + 274 +// When steer_enabled == true: + 275 +// Enter → Submitted (steer immediately) + 276 +// Tab → Queued (wait for turn end) + 277 +// + 278 +// When steer_enabled == false (legacy): + 279 +// Enter → Queued + 280 +// Tab → Queued + 281 +``` + 282 + + 283 +--- + 284 + + 285 +## Implications for OpenCode + 286 + + 287 +### What OpenCode Currently Has + 288 +- Session/turn model with `processor.ts` handling model interaction + 289 +- Parallel agents via `task.ts` tool + 290 +- No mid-turn input injection + 291 + + 292 +### What Queue/Steer Would Add + 293 +1. **Pending input buffer** on the session/turn state + 294 +2. **Steer RPC** that pushes to the buffer while model is running + 295 +3. **Loop-boundary drain** that checks for pending input after each model response + 296 +4. **Follow-up continuation** instead of ending the turn when input is pending + 297 +5. **UI queue widget** showing messages waiting for the current turn to finish + 298 +6. **Fallback path**: steer → queue if no active turn + 299 + + 300 +### Key Implementation Points + 301 +- `steer_input()` is a **lock-based, non-cancelling** approach — it doesn't abort the model stream + 302 +- Pending input is consumed at the **top of the agentic loop**, not mid-stream + 303 +- The model sees steered input as additional conversation items on its next iteration + 304 +- `expected_turn_id` prevents stale steer requests from affecting wrong turns + 305 +- Queued messages are a purely UI-side concept until they become a `turn/start` + 306 + + 307 +--- + 308 + + 309 +## References + 310 + + 311 +- Protocol types: `codex-rs/app-server-protocol/src/protocol/v2.rs` + 312 +- Core steer: `codex-rs/core/src/codex.rs` (L3377-3406) + 313 +- Turn state: `codex-rs/core/src/state/turn.rs` (L77-163) + 314 +- Task loop drain: `codex-rs/core/src/codex.rs` (L4970-5000) + 315 +- Follow-up flag: `codex-rs/core/src/codex.rs` (L6364) + 316 +- Task completion: `codex-rs/core/src/tasks/mod.rs` (L190-230) + 317 +- App server handler: `codex-rs/app-server/src/codex_message_processor.rs` + 318 +- TUI queue widget: `codex-rs/tui/src/bottom_pane/queued_user_messages.rs` + 319 +- TUI composer: `codex-rs/tui/src/public_widgets/composer_input.rs` + 320 + +``` + + +### 📄 `packages/app/src/components/prompt-input.tsx` + +**Type:** TypeScript React Component ⚛️ + +```diff +@@ -1,6 +1,6 @@ + 1 1 import { useFilteredList } from "@opencode-ai/ui/hooks" + 2 2 import { showToast } from "@opencode-ai/ui/toast" + 3 -import { createEffect, on, Component, Show, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js" + 3 +import { createEffect, on, Component, Show, For, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js" + 4 4 import { createStore } from "solid-js/store" + 5 5 import { createFocusSignal } from "@solid-primitives/active-element" + 6 6 import { useLocal } from "@/context/local" +@@ -215,6 +215,7 @@ export const PromptInput: Component = (props) => { +215 215 }, +216 216 ) +217 217 const working = createMemo(() => status()?.type !== "idle") + 218 + const steerQueue = createMemo(() => sync.data.steer_queue[params.id ?? ""] ?? []) +218 219 const imageAttachments = createMemo(() => +219 220 prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"), +220 221 ) +@@ -987,9 +988,37 @@ export const PromptInput: Component = (props) => { +987 988 } +988 989 } +989 990 +990 - // Handle Shift+Enter BEFORE IME check - Shift+Enter is never used for IME input +991 - // and should always insert a newline regardless of composition state + 991 + // Handle Shift+Enter: when working with text, send as "steer" (inject mid-turn) +992 992 if (event.key === "Enter" && event.shiftKey) { + 993 + if (working() && params.id && store.mode === "normal") { + 994 + const text = prompt + 995 + .current() + 996 + .map((p) => ("content" in p ? p.content : "")) + 997 + .join("") + 998 + .trim() + 999 + if (text.length > 0) { + 1000 + event.preventDefault() + 1001 + const sessionID = params.id + 1002 + fetch(`${sdk.url}/session/${sessionID}/steer`, { + 1003 + method: "POST", + 1004 + headers: { "Content-Type": "application/json" }, + 1005 + body: JSON.stringify({ text, mode: "steer" }), + 1006 + }).catch(() => { + 1007 + showToast({ + 1008 + title: "Failed to steer", + 1009 + description: "Could not inject message into current turn", + 1010 + }) + 1011 + }) + 1012 + prompt.reset() + 1013 + clearEditor() + 1014 + showToast({ + 1015 + title: "Steering", + 1016 + description: "Will be injected at the next step of the current turn", + 1017 + }) + 1018 + return + 1019 + } + 1020 + } + 1021 + // Default: insert newline when not working +993 1022 addPart({ type: "text", content: "\n", start: 0, end: 0 }) +994 1023 event.preventDefault() +995 1024 return +@@ -1056,6 +1085,38 @@ export const PromptInput: Component = (props) => { +1056 1085 +1057 1086 // Note: Shift+Enter is handled earlier, before IME check +1058 1087 if (event.key === "Enter" && !event.shiftKey) { + 1088 + // When busy: Enter queues a steer message instead of normal submit + 1089 + if (working() && params.id && store.mode === "normal") { + 1090 + const text = prompt + 1091 + .current() + 1092 + .map((p) => ("content" in p ? p.content : "")) + 1093 + .join("") + 1094 + .trim() + 1095 + if (text.length > 0) { + 1096 + event.preventDefault() + 1097 + const sessionID = params.id + 1098 + fetch(`${sdk.url}/session/${sessionID}/steer`, { + 1099 + method: "POST", + 1100 + headers: { "Content-Type": "application/json" }, + 1101 + body: JSON.stringify({ text }), + 1102 + }).catch(() => { + 1103 + showToast({ + 1104 + title: "Failed to queue message", + 1105 + description: "Could not steer the session", + 1106 + }) + 1107 + }) + 1108 + prompt.reset() + 1109 + clearEditor() + 1110 + showToast({ + 1111 + title: "Message queued", + 1112 + description: "Will be injected when the model finishes its current step", + 1113 + }) + 1114 + return + 1115 + } + 1116 + // Empty text while working → abort + 1117 + abort() + 1118 + return + 1119 + } +1059 1120 handleSubmit(event) +1060 1121 } +1061 1122 } +@@ -1113,6 +1174,35 @@ export const PromptInput: Component = (props) => { +1113 1174 onRemove={removeImageAttachment} +1114 1175 removeLabel={language.t("prompt.attachment.remove")} +1115 1176 /> + 1177 + 0}> + 1178 +
+ 1179 +
+ 1180 + + 1181 + Queued ({steerQueue().length}) + 1182 +
+ 1183 + + 1184 + {(item) => ( + 1185 +
+ 1186 + {item.text} + 1187 + + 1201 +
+ 1202 + )} + 1203 +
+ 1204 +
+ 1205 +
+1116 1206
{ +@@ -1206,37 +1296,135 @@ export const PromptInput: Component = (props) => { +1206 1296 +1207 1297 +1208 1298 +1209 - +1214 - +1215 -
+1216 - {language.t("prompt.action.stop")} +1217 - {language.t("common.key.esc")} +1218 -
+1219 -
+1220 - +1221 -
+1222 - {language.t("prompt.action.send")} +1223 - +1224 -
+1225 -
+1226 - +1227 - } +1228 - > +1229 - +1239 -
+ 1299 + + 1300 + + 1301 +
+ 1302 + + 1306 +
+ 1307 + Queue + 1308 + + 1309 +
+ 1310 + Send after current response finishes + 1311 +
+ 1312 + } + 1313 + > + 1314 + { + 1322 + const text = prompt + 1323 + .current() + 1324 + .map((p) => ("content" in p ? p.content : "")) + 1325 + .join("") + 1326 + .trim() + 1327 + if (!text || !params.id) return + 1328 + fetch(`${sdk.url}/session/${params.id}/steer`, { + 1329 + method: "POST", + 1330 + headers: { "Content-Type": "application/json" }, + 1331 + body: JSON.stringify({ text, mode: "queue" }), + 1332 + }).catch(() => { + 1333 + showToast({ + 1334 + title: "Failed to queue message", + 1335 + description: "Could not queue the message", + 1336 + }) + 1337 + }) + 1338 + prompt.reset() + 1339 + clearEditor() + 1340 + showToast({ + 1341 + title: "Message queued", + 1342 + description: "Will be sent when the model finishes its current response", + 1343 + }) + 1344 + }} + 1345 + /> + 1346 + + 1347 + + 1351 +
+ 1352 + Steer + 1353 + ⇧⏎ + 1354 +
+ 1355 + Inject into current turn at next step + 1356 +
+ 1357 + } + 1358 + > + 1359 + { + 1367 + const text = prompt + 1368 + .current() + 1369 + .map((p) => ("content" in p ? p.content : "")) + 1370 + .join("") + 1371 + .trim() + 1372 + if (!text || !params.id) return + 1373 + fetch(`${sdk.url}/session/${params.id}/steer`, { + 1374 + method: "POST", + 1375 + headers: { "Content-Type": "application/json" }, + 1376 + body: JSON.stringify({ text, mode: "steer" }), + 1377 + }).catch(() => { + 1378 + showToast({ + 1379 + title: "Failed to steer", + 1380 + description: "Could not inject message into current turn", + 1381 + }) + 1382 + }) + 1383 + prompt.reset() + 1384 + clearEditor() + 1385 + showToast({ + 1386 + title: "Steering", + 1387 + description: "Will be injected at the next step of the current turn", + 1388 + }) + 1389 + }} + 1390 + /> + 1391 + + 1392 + + 1393 + + 1394 + + 1395 + + 1400 + + 1401 +
+ 1402 + {language.t("prompt.action.stop")} + 1403 + {language.t("common.key.esc")} + 1404 +
+ 1405 +
+ 1406 + + 1407 +
+ 1408 + {language.t("prompt.action.send")} + 1409 + + 1410 +
+ 1411 +
+ 1412 + + 1413 + } + 1414 + > + 1415 + + 1425 +
+ 1426 +
+ 1427 + +1240 1428 +1241 1429 +1242 1430 +1243 1431 + +``` + + +### 📄 `packages/app/src/context/global-sync/child-store.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -167,6 +167,7 @@ export function createChildStoreManager(input: { +167 167 session: [], +168 168 sessionTotal: 0, +169 169 session_status: {}, + 170 + steer_queue: {}, +170 171 session_diff: {}, +171 172 todo: {}, +172 173 permission: {}, +173 174 + +``` + + +### 📄 `packages/app/src/context/global-sync/event-reducer.test.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -75,6 +75,7 @@ const baseState = (input: Partial = {}) => + 75 75 todo: {}, + 76 76 permission: {}, + 77 77 question: {}, + 78 + steer_queue: {}, + 78 79 mcp: {}, + 79 80 lsp: [], + 80 81 vcs: undefined, + 81 82 + +``` + + +### 📄 `packages/app/src/context/global-sync/event-reducer.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -52,7 +52,8 @@ function cleanupSessionCaches( + 52 52 store.todo[sessionID] !== undefined || + 53 53 store.permission[sessionID] !== undefined || + 54 54 store.question[sessionID] !== undefined || + 55 - store.session_status[sessionID] !== undefined + 55 + store.session_status[sessionID] !== undefined || + 56 + store.steer_queue[sessionID] !== undefined + 56 57 setSessionTodo?.(sessionID, undefined) + 57 58 if (!hasAny) return + 58 59 setStore( +@@ -71,6 +72,7 @@ function cleanupSessionCaches( + 71 72 delete draft.permission[sessionID] + 72 73 delete draft.question[sessionID] + 73 74 delete draft.session_status[sessionID] + 75 + delete draft.steer_queue[sessionID] + 74 76 }), + 75 77 ) + 76 78 } +@@ -164,6 +166,11 @@ export function applyDirectoryEvent(input: { +164 166 input.setStore("session_status", props.sessionID, reconcile(props.status)) +165 167 break +166 168 } + 169 + case "session.queue.changed": { + 170 + const props = event.properties as { sessionID: string; queue: { id: string; text: string; time: number; mode: "queue" | "steer" }[] } + 171 + input.setStore("steer_queue", props.sessionID, reconcile(props.queue, { key: "id" })) + 172 + break + 173 + } +167 174 case "message.updated": { +168 175 const info = (event.properties as { info: Message }).info +169 176 const messages = input.store.message[info.sessionID] +170 177 + +``` + + +### 📄 `packages/app/src/context/global-sync/types.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -46,6 +46,9 @@ export type State = { + 46 46 session_status: { + 47 47 [sessionID: string]: SessionStatus + 48 48 } + 49 + steer_queue: { + 50 + [sessionID: string]: { id: string; text: string; time: number; mode: "queue" | "steer" }[] + 51 + } + 49 52 session_diff: { + 50 53 [sessionID: string]: FileDiff[] + 51 54 } + 52 55 + +``` + + +### 📄 `packages/opencode/src/server/routes/session.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -14,6 +14,7 @@ import { Agent } from "../../agent/agent" + 14 14 import { Snapshot } from "@/snapshot" + 15 15 import { Log } from "../../util/log" + 16 16 import { PermissionNext } from "@/permission/next" + 17 +import { SessionSteer } from "@/session/steer" + 17 18 import { errors } from "../error" + 18 19 import { lazy } from "../../util/lazy" + 19 20 +@@ -933,6 +934,122 @@ export const SessionRoutes = lazy(() => +933 934 return c.json(session) +934 935 }, +935 936 ) + 937 + .post( + 938 + "/:sessionID/steer", + 939 + describeRoute({ + 940 + summary: "Steer session", + 941 + description: + 942 + "Push a message into the session's pending input buffer. If the session is busy, the message will be injected at the next agentic loop boundary. If idle, it is queued for the next turn.", + 943 + operationId: "session.steer", + 944 + responses: { + 945 + 200: { + 946 + description: "Queued message", + 947 + content: { + 948 + "application/json": { + 949 + schema: resolver( + 950 + z.object({ + 951 + id: z.string(), + 952 + text: z.string(), + 953 + time: z.number(), + 954 + mode: z.enum(["queue", "steer"]), + 955 + }), + 956 + ), + 957 + }, + 958 + }, + 959 + }, + 960 + ...errors(400, 404), + 961 + }, + 962 + }), + 963 + validator( + 964 + "param", + 965 + z.object({ + 966 + sessionID: z.string().meta({ description: "Session ID" }), + 967 + }), + 968 + ), + 969 + validator( + 970 + "json", + 971 + z.object({ + 972 + text: z.string().min(1).meta({ description: "The message text to inject" }), + 973 + mode: z.enum(["queue", "steer"]).optional().default("queue").meta({ description: "queue waits for turn end, steer injects mid-turn" }), + 974 + }), + 975 + ), + 976 + async (c) => { + 977 + const sessionID = c.req.valid("param").sessionID + 978 + const body = c.req.valid("json") + 979 + const entry = SessionSteer.push(sessionID, body.text, body.mode) + 980 + return c.json(entry) + 981 + }, + 982 + ) + 983 + .get( + 984 + "/:sessionID/steer", + 985 + describeRoute({ + 986 + summary: "Get steer queue", + 987 + description: "List all pending steered messages for a session without draining the queue.", + 988 + operationId: "session.steer.list", + 989 + responses: { + 990 + 200: { + 991 + description: "Pending steered messages", + 992 + content: { + 993 + "application/json": { + 994 + schema: resolver( + 995 + z.array( + 996 + z.object({ + 997 + id: z.string(), + 998 + text: z.string(), + 999 + time: z.number(), + 1000 + mode: z.enum(["queue", "steer"]), + 1001 + }), + 1002 + ), + 1003 + ), + 1004 + }, + 1005 + }, + 1006 + }, + 1007 + ...errors(400, 404), + 1008 + }, + 1009 + }), + 1010 + validator( + 1011 + "param", + 1012 + z.object({ + 1013 + sessionID: z.string().meta({ description: "Session ID" }), + 1014 + }), + 1015 + ), + 1016 + async (c) => { + 1017 + const sessionID = c.req.valid("param").sessionID + 1018 + const queue = SessionSteer.list(sessionID) + 1019 + return c.json(queue) + 1020 + }, + 1021 + ) + 1022 + .delete( + 1023 + "/:sessionID/steer/:steerID", + 1024 + describeRoute({ + 1025 + summary: "Remove steered message", + 1026 + description: "Remove a specific queued steered message by its ID before it gets injected.", + 1027 + operationId: "session.steer.remove", + 1028 + responses: { + 1029 + 200: { + 1030 + description: "Whether the message was found and removed", + 1031 + content: { + 1032 + "application/json": { + 1033 + schema: resolver(z.boolean()), + 1034 + }, + 1035 + }, + 1036 + }, + 1037 + ...errors(400, 404), + 1038 + }, + 1039 + }), + 1040 + validator( + 1041 + "param", + 1042 + z.object({ + 1043 + sessionID: z.string().meta({ description: "Session ID" }), + 1044 + steerID: z.string().meta({ description: "Steer message ID" }), + 1045 + }), + 1046 + ), + 1047 + async (c) => { + 1048 + const params = c.req.valid("param") + 1049 + const removed = SessionSteer.remove(params.sessionID, params.steerID) + 1050 + return c.json(removed) + 1051 + }, + 1052 + ) +936 1053 .post( +937 1054 "/:sessionID/permissions/:permissionID", +938 1055 describeRoute({ +939 1056 + +``` + + +### 📄 `packages/opencode/src/session/prompt.ts` + +**Type:** TypeScript Source File 📘 + +```diff +@@ -45,6 +45,7 @@ import { LLM } from "./llm" + 45 45 import { iife } from "@/util/iife" + 46 46 import { Shell } from "@/shell/shell" + 47 47 import { Truncate } from "@/tool/truncation" + 48 +import { SessionSteer } from "./steer" + 48 49 + 49 50 // @ts-ignore + 50 51 globalThis.AI_SDK_LOG_WARNINGS = false +@@ -320,6 +321,56 @@ export namespace SessionPrompt { +320 321 !["tool-calls", "unknown"].includes(lastAssistant.finish) && +321 322 lastUser.id < lastAssistant.id +322 323 ) { + 324 + // Check for "steer" mode messages — these inject mid-turn at loop + 325 + // boundaries. "queue" mode messages wait until the turn fully ends. + 326 + const steered = SessionSteer.takeByMode(sessionID, "steer") + 327 + if (steered.length > 0) { + 328 + log.info("steer: injecting pending input", { sessionID, count: steered.length }) + 329 + const text = steered.map((m) => m.text).join("\n\n") + 330 + const steerMsg: MessageV2.User = { + 331 + id: Identifier.ascending("message"), + 332 + sessionID, + 333 + role: "user", + 334 + time: { created: Date.now() }, + 335 + agent: lastUser.agent, + 336 + model: lastUser.model, + 337 + } + 338 + await Session.updateMessage(steerMsg) + 339 + await Session.updatePart({ + 340 + id: Identifier.ascending("part"), + 341 + messageID: steerMsg.id, + 342 + sessionID, + 343 + type: "text", + 344 + text, + 345 + } satisfies MessageV2.TextPart) + 346 + continue + 347 + } + 348 + + 349 + // Turn is finished. Drain "queue" mode messages and auto-submit + 350 + // them as new user messages so the model starts a fresh turn. + 351 + const queued = SessionSteer.takeByMode(sessionID, "queue") + 352 + if (queued.length > 0) { + 353 + log.info("steer: auto-submitting queued input", { sessionID, count: queued.length }) + 354 + const text = queued.map((m) => m.text).join("\n\n") + 355 + const queueMsg: MessageV2.User = { + 356 + id: Identifier.ascending("message"), + 357 + sessionID, + 358 + role: "user", + 359 + time: { created: Date.now() }, + 360 + agent: lastUser.agent, + 361 + model: lastUser.model, + 362 + } + 363 + await Session.updateMessage(queueMsg) + 364 + await Session.updatePart({ + 365 + id: Identifier.ascending("part"), + 366 + messageID: queueMsg.id, + 367 + sessionID, + 368 + type: "text", + 369 + text, + 370 + } satisfies MessageV2.TextPart) + 371 + continue + 372 + } + 373 + +323 374 log.info("exiting loop", { sessionID }) +324 375 break +325 376 } +326 377 + +``` + + +### ✨ `packages/opencode/src/session/steer.ts` **[ADDED]** + +**Status:** ✅ **NEW FILE** - This file has been newly created + +**Type:** TypeScript Source File 📘 + +```diff +@@ -0,0 +1,127 @@ + 1 +import { Bus } from "../bus" + 2 +import { BusEvent } from "../bus/bus-event" + 3 +import { Instance } from "../project/instance" + 4 +import { Log } from "../util/log" + 5 +import z from "zod" + 6 + + 7 +export namespace SessionSteer { + 8 + const log = Log.create({ service: "session.steer" }) + 9 + + 10 + export type Mode = "queue" | "steer" + 11 + + 12 + const QueuedMessageSchema = z.object({ + 13 + id: z.string(), + 14 + text: z.string(), + 15 + time: z.number(), + 16 + mode: z.enum(["queue", "steer"]), + 17 + }) + 18 + + 19 + export const Event = { + 20 + QueueChanged: BusEvent.define( + 21 + "session.queue.changed", + 22 + z.object({ + 23 + sessionID: z.string(), + 24 + queue: z.array(QueuedMessageSchema), + 25 + }), + 26 + ), + 27 + } + 28 + + 29 + export interface QueuedMessage { + 30 + id: string + 31 + text: string + 32 + time: number + 33 + mode: Mode + 34 + } + 35 + + 36 + interface SteerState { + 37 + pending: QueuedMessage[] + 38 + } + 39 + + 40 + const state = Instance.state( + 41 + () => { + 42 + const data: Record = {} + 43 + return data + 44 + }, + 45 + async () => {}, + 46 + ) + 47 + + 48 + function ensure(sessionID: string): SteerState { + 49 + const s = state() + 50 + if (!s[sessionID]) s[sessionID] = { pending: [] } + 51 + return s[sessionID] + 52 + } + 53 + + 54 + /** Push a message into the pending buffer for an active session. */ + 55 + export function push(sessionID: string, text: string, mode: Mode = "queue"): QueuedMessage { + 56 + const entry: QueuedMessage = { + 57 + id: crypto.randomUUID(), + 58 + text, + 59 + time: Date.now(), + 60 + mode, + 61 + } + 62 + const s = ensure(sessionID) + 63 + s.pending.push(entry) + 64 + log.info("steer.push", { sessionID, id: entry.id, queueLength: s.pending.length }) + 65 + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + 66 + return entry + 67 + } + 68 + + 69 + /** Drain all pending messages and return them. Clears the buffer. */ + 70 + export function take(sessionID: string): QueuedMessage[] { + 71 + const s = state()[sessionID] + 72 + if (!s || s.pending.length === 0) return [] + 73 + const result = s.pending.splice(0) + 74 + log.info("steer.take", { sessionID, count: result.length }) + 75 + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + 76 + return result + 77 + } + 78 + + 79 + /** Drain only messages matching the given mode. Leaves other messages in the buffer. */ + 80 + export function takeByMode(sessionID: string, mode: Mode): QueuedMessage[] { + 81 + const s = state()[sessionID] + 82 + if (!s || s.pending.length === 0) return [] + 83 + const matched: QueuedMessage[] = [] + 84 + const remaining: QueuedMessage[] = [] + 85 + for (const m of s.pending) { + 86 + if (m.mode === mode) matched.push(m) + 87 + else remaining.push(m) + 88 + } + 89 + if (matched.length === 0) return [] + 90 + s.pending = remaining + 91 + log.info("steer.takeByMode", { sessionID, mode, count: matched.length }) + 92 + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + 93 + return matched + 94 + } + 95 + + 96 + /** Check if there's pending steered input for a session. */ + 97 + export function has(sessionID: string): boolean { + 98 + const s = state()[sessionID] + 99 + return !!s && s.pending.length > 0 + 100 + } + 101 + + 102 + /** Get the current queue without draining. */ + 103 + export function list(sessionID: string): QueuedMessage[] { + 104 + return state()[sessionID]?.pending ?? [] + 105 + } + 106 + + 107 + /** Remove a specific queued message by id. */ + 108 + export function remove(sessionID: string, id: string): boolean { + 109 + const s = state()[sessionID] + 110 + if (!s) return false + 111 + const idx = s.pending.findIndex((m) => m.id === id) + 112 + if (idx === -1) return false + 113 + s.pending.splice(idx, 1) + 114 + log.info("steer.remove", { sessionID, id }) + 115 + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + 116 + return true + 117 + } + 118 + + 119 + /** Clear all pending messages for a session. */ + 120 + export function clear(sessionID: string) { + 121 + const s = state()[sessionID] + 122 + if (!s || s.pending.length === 0) return + 123 + s.pending.length = 0 + 124 + log.info("steer.clear", { sessionID }) + 125 + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + 126 + } + 127 +} + 128 + +``` + + +### ✨ `packages/opencode/test/session/steer.test.ts` **[ADDED]** + +**Status:** ✅ **NEW FILE** - This file has been newly created + +**Type:** TypeScript Source File 📘 + +```diff +@@ -0,0 +1,235 @@ + 1 +import { describe, expect, test, beforeEach } from "bun:test" + 2 +import path from "path" + 3 +import { SessionSteer } from "../../src/session/steer" + 4 +import { Instance } from "../../src/project/instance" + 5 +import { Log } from "../../src/util/log" + 6 + + 7 +const projectRoot = path.join(__dirname, "../..") + 8 +const SESSION = "session_test_steer_001" + 9 +Log.init({ print: false }) + 10 + + 11 +/** Helper to run a test function inside Instance.provide context */ + 12 +function withInstance(fn: () => void | Promise) { + 13 + return Instance.provide({ + 14 + directory: projectRoot, + 15 + fn: async () => { + 16 + await fn() + 17 + }, + 18 + }) + 19 +} + 20 + + 21 +describe("SessionSteer", () => { + 22 + describe("push", () => { + 23 + test("creates a queued message with default mode 'queue'", async () => { + 24 + await withInstance(() => { + 25 + SessionSteer.clear(SESSION) + 26 + const msg = SessionSteer.push(SESSION, "hello") + 27 + expect(msg.text).toBe("hello") + 28 + expect(msg.mode).toBe("queue") + 29 + expect(msg.id).toBeTruthy() + 30 + expect(msg.time).toBeGreaterThan(0) + 31 + }) + 32 + }) + 33 + + 34 + test("accepts explicit mode 'steer'", async () => { + 35 + await withInstance(() => { + 36 + SessionSteer.clear(SESSION) + 37 + const msg = SessionSteer.push(SESSION, "redirect", "steer") + 38 + expect(msg.text).toBe("redirect") + 39 + expect(msg.mode).toBe("steer") + 40 + }) + 41 + }) + 42 + + 43 + test("accepts explicit mode 'queue'", async () => { + 44 + await withInstance(() => { + 45 + SessionSteer.clear(SESSION) + 46 + const msg = SessionSteer.push(SESSION, "later", "queue") + 47 + expect(msg.mode).toBe("queue") + 48 + }) + 49 + }) + 50 + }) + 51 + + 52 + describe("take", () => { + 53 + test("drains all messages regardless of mode", async () => { + 54 + await withInstance(() => { + 55 + SessionSteer.clear(SESSION) + 56 + SessionSteer.push(SESSION, "a", "queue") + 57 + SessionSteer.push(SESSION, "b", "steer") + 58 + SessionSteer.push(SESSION, "c", "queue") + 59 + + 60 + const taken = SessionSteer.take(SESSION) + 61 + expect(taken).toHaveLength(3) + 62 + expect(taken.map((m) => m.text)).toEqual(["a", "b", "c"]) + 63 + expect(SessionSteer.list(SESSION)).toHaveLength(0) + 64 + }) + 65 + }) + 66 + + 67 + test("returns empty array when no messages", async () => { + 68 + await withInstance(() => { + 69 + SessionSteer.clear(SESSION) + 70 + expect(SessionSteer.take(SESSION)).toEqual([]) + 71 + }) + 72 + }) + 73 + }) + 74 + + 75 + describe("takeByMode", () => { + 76 + test("drains only 'steer' messages, leaving 'queue' messages", async () => { + 77 + await withInstance(() => { + 78 + SessionSteer.clear(SESSION) + 79 + SessionSteer.push(SESSION, "queued-1", "queue") + 80 + SessionSteer.push(SESSION, "steer-1", "steer") + 81 + SessionSteer.push(SESSION, "queued-2", "queue") + 82 + SessionSteer.push(SESSION, "steer-2", "steer") + 83 + + 84 + const steered = SessionSteer.takeByMode(SESSION, "steer") + 85 + expect(steered).toHaveLength(2) + 86 + expect(steered.map((m) => m.text)).toEqual(["steer-1", "steer-2"]) + 87 + + 88 + const remaining = SessionSteer.list(SESSION) + 89 + expect(remaining).toHaveLength(2) + 90 + expect(remaining.map((m) => m.text)).toEqual(["queued-1", "queued-2"]) + 91 + }) + 92 + }) + 93 + + 94 + test("drains only 'queue' messages, leaving 'steer' messages", async () => { + 95 + await withInstance(() => { + 96 + SessionSteer.clear(SESSION) + 97 + SessionSteer.push(SESSION, "queued-1", "queue") + 98 + SessionSteer.push(SESSION, "steer-1", "steer") + 99 + SessionSteer.push(SESSION, "queued-2", "queue") + 100 + + 101 + const queued = SessionSteer.takeByMode(SESSION, "queue") + 102 + expect(queued).toHaveLength(2) + 103 + expect(queued.map((m) => m.text)).toEqual(["queued-1", "queued-2"]) + 104 + + 105 + const remaining = SessionSteer.list(SESSION) + 106 + expect(remaining).toHaveLength(1) + 107 + expect(remaining[0].text).toBe("steer-1") + 108 + }) + 109 + }) + 110 + + 111 + test("returns empty when no messages match mode", async () => { + 112 + await withInstance(() => { + 113 + SessionSteer.clear(SESSION) + 114 + SessionSteer.push(SESSION, "queued", "queue") + 115 + const steered = SessionSteer.takeByMode(SESSION, "steer") + 116 + expect(steered).toEqual([]) + 117 + expect(SessionSteer.list(SESSION)).toHaveLength(1) + 118 + }) + 119 + }) + 120 + + 121 + test("returns empty when buffer is empty", async () => { + 122 + await withInstance(() => { + 123 + SessionSteer.clear(SESSION) + 124 + expect(SessionSteer.takeByMode(SESSION, "steer")).toEqual([]) + 125 + expect(SessionSteer.takeByMode(SESSION, "queue")).toEqual([]) + 126 + }) + 127 + }) + 128 + + 129 + test("sequential takeByMode drains both modes completely", async () => { + 130 + await withInstance(() => { + 131 + SessionSteer.clear(SESSION) + 132 + SessionSteer.push(SESSION, "s1", "steer") + 133 + SessionSteer.push(SESSION, "q1", "queue") + 134 + SessionSteer.push(SESSION, "s2", "steer") + 135 + SessionSteer.push(SESSION, "q2", "queue") + 136 + + 137 + const steered = SessionSteer.takeByMode(SESSION, "steer") + 138 + expect(steered).toHaveLength(2) + 139 + + 140 + const queued = SessionSteer.takeByMode(SESSION, "queue") + 141 + expect(queued).toHaveLength(2) + 142 + + 143 + expect(SessionSteer.has(SESSION)).toBe(false) + 144 + expect(SessionSteer.list(SESSION)).toHaveLength(0) + 145 + }) + 146 + }) + 147 + }) + 148 + + 149 + describe("has", () => { + 150 + test("returns false for empty session", async () => { + 151 + await withInstance(() => { + 152 + SessionSteer.clear(SESSION) + 153 + expect(SessionSteer.has(SESSION)).toBe(false) + 154 + }) + 155 + }) + 156 + + 157 + test("returns true after push", async () => { + 158 + await withInstance(() => { + 159 + SessionSteer.clear(SESSION) + 160 + SessionSteer.push(SESSION, "test") + 161 + expect(SessionSteer.has(SESSION)).toBe(true) + 162 + }) + 163 + }) + 164 + + 165 + test("returns false after take drains all", async () => { + 166 + await withInstance(() => { + 167 + SessionSteer.clear(SESSION) + 168 + SessionSteer.push(SESSION, "test") + 169 + SessionSteer.take(SESSION) + 170 + expect(SessionSteer.has(SESSION)).toBe(false) + 171 + }) + 172 + }) + 173 + + 174 + test("returns true when takeByMode leaves remaining", async () => { + 175 + await withInstance(() => { + 176 + SessionSteer.clear(SESSION) + 177 + SessionSteer.push(SESSION, "q", "queue") + 178 + SessionSteer.takeByMode(SESSION, "steer") + 179 + expect(SessionSteer.has(SESSION)).toBe(true) + 180 + }) + 181 + }) + 182 + }) + 183 + + 184 + describe("list", () => { + 185 + test("returns current queue without draining", async () => { + 186 + await withInstance(() => { + 187 + SessionSteer.clear(SESSION) + 188 + SessionSteer.push(SESSION, "a", "queue") + 189 + SessionSteer.push(SESSION, "b", "steer") + 190 + + 191 + const first = SessionSteer.list(SESSION) + 192 + expect(first).toHaveLength(2) + 193 + + 194 + const second = SessionSteer.list(SESSION) + 195 + expect(second).toHaveLength(2) + 196 + }) + 197 + }) + 198 + }) + 199 + + 200 + describe("remove", () => { + 201 + test("removes specific message by id", async () => { + 202 + await withInstance(() => { + 203 + SessionSteer.clear(SESSION) + 204 + const msg = SessionSteer.push(SESSION, "target", "steer") + 205 + SessionSteer.push(SESSION, "keep", "queue") + 206 + + 207 + const removed = SessionSteer.remove(SESSION, msg.id) + 208 + expect(removed).toBe(true) + 209 + expect(SessionSteer.list(SESSION)).toHaveLength(1) + 210 + expect(SessionSteer.list(SESSION)[0].text).toBe("keep") + 211 + }) + 212 + }) + 213 + + 214 + test("returns false for non-existent id", async () => { + 215 + await withInstance(() => { + 216 + SessionSteer.clear(SESSION) + 217 + SessionSteer.push(SESSION, "test") + 218 + expect(SessionSteer.remove(SESSION, "nonexistent")).toBe(false) + 219 + }) + 220 + }) + 221 + }) + 222 + + 223 + describe("clear", () => { + 224 + test("removes all pending messages", async () => { + 225 + await withInstance(() => { + 226 + SessionSteer.clear(SESSION) + 227 + SessionSteer.push(SESSION, "a", "queue") + 228 + SessionSteer.push(SESSION, "b", "steer") + 229 + SessionSteer.clear(SESSION) + 230 + expect(SessionSteer.has(SESSION)).toBe(false) + 231 + expect(SessionSteer.list(SESSION)).toHaveLength(0) + 232 + }) + 233 + }) + 234 + }) + 235 +}) + 236 + +``` + +## 🤖 Comprehensive Review Checklist + +### ✅ Code Quality & Standards +- [ ] **Syntax & Formatting**: Consistent indentation, proper spacing +- [ ] **Naming Conventions**: Clear, descriptive variable/function names +- [ ] **Code Structure**: Logical organization, appropriate function size +- [ ] **Documentation**: Clear comments explaining complex logic +- [ ] **Type Safety**: Proper typing (if applicable) + +### 🔍 Logic & Functionality +- [ ] **Algorithm Correctness**: Logic implements requirements correctly +- [ ] **Edge Case Handling**: Boundary conditions properly addressed +- [ ] **Error Handling**: Appropriate try-catch blocks and error messages +- [ ] **Performance**: Efficient algorithms, no unnecessary loops +- [ ] **Memory Management**: Proper cleanup, no memory leaks + +### 🐛 Potential Issues & Bugs +- [ ] **Runtime Errors**: No null/undefined dereferencing +- [ ] **Type Mismatches**: Consistent data types throughout +- [ ] **Race Conditions**: Proper async/await handling +- [ ] **Resource Leaks**: Event listeners, timers properly cleaned up +- [ ] **Off-by-one Errors**: Array/loop bounds correctly handled + +### 🔒 Security Considerations +- [ ] **Input Validation**: User inputs properly sanitized +- [ ] **XSS Prevention**: No unsafe HTML injection +- [ ] **Authentication**: Proper access controls if applicable +- [ ] **Data Exposure**: No sensitive information in logs/client +- [ ] **Dependency Security**: No known vulnerable packages + +### 📱 User Experience & Accessibility +- [ ] **Responsive Design**: Works on different screen sizes +- [ ] **Loading States**: Proper feedback during operations +- [ ] **Error Messages**: User-friendly error communication +- [ ] **Accessibility**: ARIA labels, keyboard navigation +- [ ] **Performance**: Fast loading, smooth interactions + +### 💡 Improvement Suggestions + +#### Code Organization +- [ ] Consider extracting complex logic into separate functions +- [ ] Evaluate if constants should be moved to configuration +- [ ] Check for code duplication opportunities + +#### Performance Optimizations +- [ ] Identify opportunities for memoization +- [ ] Consider lazy loading for heavy operations +- [ ] Evaluate database query efficiency (if applicable) + +#### Testing Recommendations +- [ ] Unit tests for core functionality +- [ ] Integration tests for API endpoints +- [ ] Edge case testing scenarios + +#### Documentation Needs +- [ ] API documentation updates +- [ ] Code comments for complex algorithms +- [ ] README updates if public interfaces changed + +### 📝 Review Notes +*Add your specific feedback, suggestions, and observations here:* + +--- +*Individual file review generated by AI Visual Code Review v2.0* +*Generated: 2026-02-26T10:33:24.938Z* diff --git a/docs/09-temp/cline-subagent-research.md b/docs/09-temp/cline-subagent-research.md new file mode 100644 index 000000000000..76d7684e9929 --- /dev/null +++ b/docs/09-temp/cline-subagent-research.md @@ -0,0 +1,66 @@ +# Research: Cline Subagent Architecture + +**Date:** 2026-02-24 +**Status:** TODO — pick up in next session + +## Research Questions + +1. How does Cline form subagents? How does the AI decide how many to create? +2. What task distribution strategy is used? How are tasks assigned to each subagent? +3. How is context shared between parent agent and subagents? +4. How are subagent outputs aggregated back into the main conversation? +5. What happens when a subagent task errors? Error handling and recovery. +6. How could this inspire improvements to opencode's existing subagent system? + +## Key References + +- **CLI Subagent Command Transformation**: `src/integrations/cli-subagents/subagent_command.ts` + - `isSubagentCommand()` — identifies simplified cline commands + - `transformClineCommand()` — injects `--json -y` flags for autonomous execution + +- **Agent Client Protocol (ACP)**: `cli/src/acp/AcpAgent.ts` + - Bridges ClineAgent with AgentSideConnection for stdio-based communication + - Handles permission requests, forwards session events + +- **ClineAgent**: `cli/src/agent/ClineAgent.ts` + - Implements ACP agent interface + - Translates ACP requests into core Controller operations + - Manages authentication, session modes, processes user prompts + +- **Message Translator**: `cli/src/agent/messageTranslator.ts` + - Converts ClineMessage objects to ACP SessionUpdate messages + - Computes deltas for streaming (avoids duplicate content) + +## CodeWiki References + +- https://codewiki.google/github.com/cline/cline#cli-subagent-command-transformation +- https://codewiki.google/github.com/cline/cline#command-line-interface-cli-functionality +- https://codewiki.google/github.com/cline/cline#agent-client-protocol-acp-integration-for-external-control + +## Comparison with OpenCode's Subagent System + +OpenCode already has subagents (`TaskTool` in `packages/opencode/src/tool/task.ts`): +- Subagents are spawned via the `task` tool +- Each subagent gets its own child session +- Subagent types: explore, plan, general (configurable per agent) +- Results returned as tool output to parent session + +**Gaps to investigate:** +- Does Cline support parallel subagents? (OpenCode does via plan mode Phase 1) +- How does Cline's ACP protocol compare to opencode's Bus event system? +- Can we adopt Cline's streaming delta pattern for subagent updates? + +## Tonight's Session Summary (2026-02-24, 2:37 AM - 4:57 AM) + +### 6 PRs Submitted to opencode (sst/opencode): +1. **#14820** — Streaming content duplication fix (global-sdk.tsx voided Set) +2. **#14821** — Font size settings (CSS vars + terminal + UI stepper) +3. **#14826** — ContextOverflowError auto-recovery (processor.ts) +4. **#14827** — Prune before compaction (prompt.ts) +5. **#14831** — Context usage card with compact button (session-context-tab.tsx) +6. **#14835** — Wide mode setting (full-width chat toggle) + +### Issues Created: +- #14822, #14823, #14824, #14825, #14830, #14834 + +### All branches merged into `origin/dev` on fork (PrakharMNNIT/opencode) diff --git a/docs/09-temp/codex-queue-steer-architecture.md b/docs/09-temp/codex-queue-steer-architecture.md new file mode 100644 index 000000000000..f2012489aa4f --- /dev/null +++ b/docs/09-temp/codex-queue-steer-architecture.md @@ -0,0 +1,319 @@ +# Codex Queue/Steer Architecture Analysis + +> Deep-dive into OpenAI Codex CLI's queue/steer mechanism for mid-turn user interaction. +> Source: `references/codex/` submodule + +--- + +## Overview + +Codex implements a **dual-input model** that lets users interact with the agent **during** an active turn, not just between turns: + +| Action | Keybinding | Behavior | When Turn Active | +|--------|-----------|----------|-----------------| +| **Queue** | `Enter` | Enqueue message for next turn boundary | Message waits in queue, displayed in UI | +| **Steer** | `⌘Enter` / `Enter` (steer-mode) | Inject input into active turn immediately | Message sent to model in current context | + +--- + +## Architecture Layers + +``` +┌─────────────────────────────────────────────────┐ +│ TUI Layer (tui/src/) │ +│ ┌─────────────────────────────────────────┐ │ +│ │ ChatComposer │ │ +│ │ Enter → InputResult::Submitted (steer) │ │ +│ │ Tab → InputResult::Queued │ │ +│ └─────────────────┬───────────────────────┘ │ +│ │ │ +│ ┌─────────────────▼───────────────────────┐ │ +│ │ QueuedUserMessages widget │ │ +│ │ Shows queued messages with "↳" prefix │ │ +│ │ Alt+Up to pop back into composer │ │ +│ └─────────────────────────────────────────┘ │ +└────────────────────┬────────────────────────────┘ + │ +┌────────────────────▼────────────────────────────┐ +│ App Server Protocol (app-server-protocol/) │ +│ │ +│ turn/start → TurnStartParams (new turn) │ +│ turn/steer → TurnSteerParams (mid-turn) │ +│ │ +│ TurnSteerParams { │ +│ thread_id: String, │ +│ input: Vec, │ +│ expected_turn_id: String, // guard │ +│ } │ +│ │ +│ TurnSteerResponse { │ +│ turn_id: String, // confirms active turn │ +│ } │ +└────────────────────┬────────────────────────────┘ + │ +┌────────────────────▼────────────────────────────┐ +│ App Server (app-server/src/) │ +│ codex_message_processor.rs │ +│ │ +│ async fn turn_steer(&self, req_id, params) { │ +│ let thread = load_thread(params.thread_id); │ +│ thread.steer_input( │ +│ mapped_items, │ +│ Some(¶ms.expected_turn_id) │ +│ ); │ +│ // Returns turn_id or error: │ +│ // "no active turn to steer" │ +│ } │ +└────────────────────┬────────────────────────────┘ + │ +┌────────────────────▼────────────────────────────┐ +│ Core Engine (core/src/codex.rs) │ +│ │ +│ Session::steer_input(input, expected_turn_id) │ +│ 1. Validate input not empty │ +│ 2. Lock active_turn mutex │ +│ 3. Verify active turn exists │ +│ 4. Check expected_turn_id matches │ +│ 5. Lock turn_state │ +│ 6. push_pending_input(input) ← KEY STEP │ +│ 7. Return active turn_id │ +└────────────────────┬────────────────────────────┘ + │ +┌────────────────────▼────────────────────────────┐ +│ Turn State (core/src/state/turn.rs) │ +│ │ +│ struct TurnState { │ +│ pending_input: Vec, │ +│ } │ +│ │ +│ push_pending_input(item) → appends to vec │ +│ take_pending_input() → drains vec │ +│ has_pending_input() → checks non-empty │ +└────────────────────┬────────────────────────────┘ + │ +┌────────────────────▼────────────────────────────┐ +│ Task Loop (core/src/codex.rs ~L4970) │ +│ │ +│ loop { │ +│ // At each iteration, drain pending input │ +│ let pending = sess.get_pending_input().await; │ +│ if !pending.is_empty() { │ +│ // Record as conversation items │ +│ // → injected into model context │ +│ for item in pending { │ +│ record_user_prompt_and_emit_turn_item(); │ +│ } │ +│ } │ +│ // ... send to model, process response ... │ +│ // On ResponseEvent::Completed: │ +│ needs_follow_up |= has_pending_input(); │ +│ // If follow_up needed → loop continues │ +│ } │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## Core Mechanism: `steer_input` + +The heart of steer is `Session::steer_input()` in `core/src/codex.rs`: + +```rust +pub async fn steer_input( + &self, + input: Vec, + expected_turn_id: Option<&str>, +) -> Result { + if input.is_empty() { + return Err(SteerInputError::EmptyInput); + } + let mut active = self.active_turn.lock().await; + let Some(active_turn) = active.as_mut() else { + return Err(SteerInputError::NoActiveTurn(input)); + }; + let Some((active_turn_id, _)) = active_turn.tasks.first() else { + return Err(SteerInputError::NoActiveTurn(input)); + }; + if let Some(expected_turn_id) = expected_turn_id + && expected_turn_id != active_turn_id + { + return Err(SteerInputError::ExpectedTurnMismatch { + expected: expected_turn_id.to_string(), + actual: active_turn_id.clone(), + }); + } + let mut turn_state = active_turn.turn_state.lock().await; + turn_state.push_pending_input(input.into()); + Ok(active_turn_id.clone()) +} +``` + +### Key Design Decisions + +1. **Non-blocking injection**: `steer_input` just pushes to a `Vec` — it doesn't interrupt or cancel the model. The model's active response completes naturally. + +2. **Consumption at loop boundary**: The task loop checks `pending_input` at the **top of each iteration**. After the model finishes a response, if pending input exists, it gets recorded as conversation items and the model is called again with the updated context. + +3. **`needs_follow_up` flag**: When a model response completes (`ResponseEvent::Completed`), if there's pending input, the loop sets `needs_follow_up = true` and continues instead of ending the turn. + +4. **Turn ID validation**: The `expected_turn_id` field prevents race conditions — the steer request fails if the turn has changed between the user pressing Enter and the server processing the request. + +--- + +## Error Types + +```rust +pub enum SteerInputError { + NoActiveTurn(Vec), // No model turn running + ExpectedTurnMismatch { // Turn changed since request + expected: String, + actual: String, + }, + EmptyInput, // Nothing to inject +} +``` + +When `NoActiveTurn` occurs, the app-server falls back — the input that failed to steer gets queued for the next `turn/start`. + +--- + +## Queue vs Steer: Detailed Comparison + +### Queue (Tab / Enter in legacy mode) + +1. User types message, presses Tab (or Enter in non-steer mode) +2. TUI returns `InputResult::Queued { text, text_elements }` +3. Message stored in `QueuedUserMessages.messages: Vec` +4. Rendered in UI with `↳` prefix, dimmed/italic +5. User can pop with Alt+Up to edit +6. When current turn completes → queued messages become the next `turn/start` + +### Steer (Enter in steer mode / ⌘Enter) + +1. User types message, presses Enter +2. TUI returns `InputResult::Submitted { text, text_elements }` +3. App sends `turn/steer` RPC to server +4. Server calls `thread.steer_input()` → pushes to `pending_input` +5. Model's current response continues to completion +6. At next task loop iteration, pending input is drained and recorded +7. Model sees the user's steer message in context → generates follow-up +8. **All within the same turn** — no new turn boundary + +### Critical Difference + +| Aspect | Queue | Steer | +|--------|-------|-------| +| **Timing** | After turn ends | During active turn | +| **Turn boundary** | Creates new turn | Same turn continues | +| **Model sees it** | On next turn start | At next loop iteration | +| **Cancels response** | No (waits) | No (appends to context) | +| **UI display** | Queued messages widget | Injected into chat transcript | +| **Fallback** | N/A | Falls back to queue if no active turn | + +--- + +## Turn Lifecycle with Steer + +``` +Turn Start (user submits prompt) + │ + ├─→ Model generates response... + │ │ + │ │ ← User presses Enter (steer) + │ │ → steer_input() pushes to pending_input + │ │ + │ ▼ + │ Response completes + │ │ + │ ├─→ has_pending_input()? YES + │ │ → needs_follow_up = true + │ │ + │ ▼ + │ Loop continues → drain pending_input + │ → Record steered message as conversation item + │ → Model sees: [original prompt, response, steered message] + │ → Model generates new response with full context + │ │ + │ ├─→ has_pending_input()? NO + │ │ → needs_follow_up = false + │ ▼ + │ Turn Complete + │ + └─→ Queued messages (if any) → next turn/start +``` + +--- + +## Turn Completion & Leftover Input + +When a task finishes (`task_finished()` in `core/src/tasks/mod.rs`): + +```rust +// 1. Lock active turn +let mut active = self.active_turn.lock().await; +// 2. Take any remaining pending input +let pending_input = ts.take_pending_input(); +// 3. Clear active turn +*active = None; +// 4. Record leftover input as conversation items +if !pending_input.is_empty() { + record_conversation_items(&turn_context, &pending_response_items); +} +// 5. Emit TurnComplete event +``` + +This ensures steered input is **never lost** — even if the turn ends before the pending input could be consumed by the model loop. + +--- + +## Feature Flag: `steer_enabled` + +Steer is gated behind `Feature::Steer` in the TUI: + +```rust +// When steer_enabled == true: +// Enter → Submitted (steer immediately) +// Tab → Queued (wait for turn end) +// +// When steer_enabled == false (legacy): +// Enter → Queued +// Tab → Queued +``` + +--- + +## Implications for OpenCode + +### What OpenCode Currently Has +- Session/turn model with `processor.ts` handling model interaction +- Parallel agents via `task.ts` tool +- No mid-turn input injection + +### What Queue/Steer Would Add +1. **Pending input buffer** on the session/turn state +2. **Steer RPC** that pushes to the buffer while model is running +3. **Loop-boundary drain** that checks for pending input after each model response +4. **Follow-up continuation** instead of ending the turn when input is pending +5. **UI queue widget** showing messages waiting for the current turn to finish +6. **Fallback path**: steer → queue if no active turn + +### Key Implementation Points +- `steer_input()` is a **lock-based, non-cancelling** approach — it doesn't abort the model stream +- Pending input is consumed at the **top of the agentic loop**, not mid-stream +- The model sees steered input as additional conversation items on its next iteration +- `expected_turn_id` prevents stale steer requests from affecting wrong turns +- Queued messages are a purely UI-side concept until they become a `turn/start` + +--- + +## References + +- Protocol types: `codex-rs/app-server-protocol/src/protocol/v2.rs` +- Core steer: `codex-rs/core/src/codex.rs` (L3377-3406) +- Turn state: `codex-rs/core/src/state/turn.rs` (L77-163) +- Task loop drain: `codex-rs/core/src/codex.rs` (L4970-5000) +- Follow-up flag: `codex-rs/core/src/codex.rs` (L6364) +- Task completion: `codex-rs/core/src/tasks/mod.rs` (L190-230) +- App server handler: `codex-rs/app-server/src/codex_message_processor.rs` +- TUI queue widget: `codex-rs/tui/src/bottom_pane/queued_user_messages.rs` +- TUI composer: `codex-rs/tui/src/public_widgets/composer_input.rs` diff --git a/docs/09-temp/escape-key-ux-research.md b/docs/09-temp/escape-key-ux-research.md new file mode 100644 index 000000000000..37575a6243bb --- /dev/null +++ b/docs/09-temp/escape-key-ux-research.md @@ -0,0 +1,32 @@ +# Research: Escape Key Cancel UX + +**Date:** 2026-02-24 +**Status:** TODO — brainstorm in next session + +## Problem +Pressing Escape accidentally during AI response immediately stops the response with no confirmation. No visual feedback in chat that response was interrupted. + +## Current Behavior +- Escape → immediately cancels the LLM response +- Shows a notification/warning toast +- No visual indicator in the chat thread that the message was interrupted +- No confirmation dialog before cancelling + +## User's Proposed Improvements +1. **Confirmation before cancel** — Alert/dialog: "Are you sure you want to interrupt?" +2. **Visual interruption indicator** — Show in chat that the message was interrupted (red line, badge, etc.) +3. **Better UX** — Maybe double-tap Escape to cancel, or Escape once to show warning + +## Files to Investigate +- `packages/app/src/pages/session.tsx` — handleKeyDown, Escape handling +- `packages/app/src/components/prompt-input.tsx` — Escape key handling in input +- `packages/opencode/src/session/prompt.ts` — cancel() function +- `packages/ui/src/components/message-part.tsx` — interrupted state rendering +- `packages/app/src/pages/session/use-session-commands.tsx` — session.cancel command + +## Design Questions +1. Should Escape require double-tap? (like VS Code terminal) +2. Should there be a small "Esc to cancel" indicator during streaming? +3. Should interrupted messages have a visual indicator (red border/badge)? +4. Should there be an "undo cancel" option (resume if possible)? +5. How does Cline/Cursor handle this? diff --git a/docs/09-temp/issues.md b/docs/09-temp/issues.md new file mode 100644 index 000000000000..263f75fe81c8 --- /dev/null +++ b/docs/09-temp/issues.md @@ -0,0 +1,599 @@ +# OpenCode — Parallel Agent & Retry Storm Issues + +> **Created**: 2025-02-25 +> **Source**: Combined RCA by Cline + Antigravity +> **Status**: Approved for implementation + +--- + +## Issue #1: `processor-max-retries` — Infinite Retry Loop in processor.ts + +### Priority: P0 — Stop The Bleeding + +### What is the issue? +The session processor retries failed API calls in an infinite `while(true)` loop with **no maximum retry count**. When an error is classified as "retryable" by `retry.ts`, the processor will retry it forever — user observed **2,244 identical retries over 3.5 hours** before manual abort. + +### What is the bug? +`packages/opencode/src/session/processor.ts` line ~53 has a `while(true)` loop. When the catch block determines an error is retryable via `SessionRetry.retryable(error)`, it increments `attempt` and `continue`s the loop. There is **no guard** like `if (attempt >= MAX_RETRIES) break`. + +### Where it can happen? +- Any API call that returns a retryable error (transient network issues, rate limits, Bedrock context overflow misclassified as retryable) +- Most critically: Bedrock "prompt is too long" errors that get misclassified as retryable by the catch-all in `retry.ts` (see Issue #2) +- Affects both parent sessions and subagent sessions independently + +### What any agent needs to look for? +``` +File: packages/opencode/src/session/processor.ts +Location: The while(true) loop (~line 53) +Pattern: Look for the catch block that calls SessionRetry.retryable() and does `continue` +``` + +### How to make the fix? +Add a `MAX_RETRIES` constant and guard before the `continue`: + +```typescript +// At top of file or inside the function +const MAX_RETRIES = 10 + +// Inside the catch block, before `continue`: +if (attempt >= MAX_RETRIES) { + input.assistantMessage.error = { + name: "RetryLimitExceeded", + message: `Maximum retries (${MAX_RETRIES}) exceeded. Last error: ${retry}`, + } + break +} +``` + +The error should be stored on `input.assistantMessage.error` so the session stops and the UI shows the error. Make sure the status is set to idle after breaking. + +### Testing +- Trigger a retryable error (e.g., rate limit) and verify it stops after 10 attempts +- Verify the error message appears in the session UI +- Verify the session status returns to "idle" (not stuck in "retry") + +--- + +## Issue #2: `bedrock-undefined-message` — error.ts Fails to Parse Bedrock Error Messages + +### Priority: P0 — Stop The Bleeding + +### What is the issue? +When Amazon Bedrock returns an API error (e.g., "prompt is too long"), the `message()` function in `error.ts` receives `e.message = "undefined"` (the literal string, not the JS undefined value). The function only checks for empty string `""`, so it passes `"undefined"` through to `isOverflow()`, which fails to match any overflow pattern. This means **Bedrock context overflow errors are never detected as overflow**, preventing compaction from triggering. + +### What is the bug? +`packages/opencode/src/provider/error.ts` function `message()` (~line 50-80): +```typescript +const msg = e.message +if (msg === "") { + if (e.responseBody) return e.responseBody + // ... +} +``` +When Bedrock SDK sets `e.message` to the literal string `"undefined"`, this check passes through. The actual error details are in `e.responseBody` but never extracted. + +### Where it can happen? +- Any Bedrock API call that returns an error (context overflow, validation errors, throttling) +- The Bedrock SDK wraps errors differently than the Anthropic direct SDK +- Specifically observed with "prompt is too long: 208845 tokens > 200000 maximum" errors + +### What any agent needs to look for? +``` +File: packages/opencode/src/provider/error.ts +Location: The message() function, specifically the `if (msg === "")` check +Also check: isOverflow() function and the OVERFLOW_PATTERNS regex +``` + +### How to make the fix? +Extend the empty-message check to also handle `"undefined"`: + +```typescript +function message(providerID: string, e: APICallError) { + return iife(() => { + const msg = e.message + if (msg === "" || msg === "undefined") { + if (e.responseBody) return e.responseBody + // ... rest of existing fallback logic + } + return msg + }) +} +``` + +This ensures the actual error body (which contains "prompt is too long") is used for overflow detection instead of the meaningless `"undefined"` string. + +### Testing +- Mock a Bedrock APICallError with `message: "undefined"` and `responseBody: "prompt is too long: 208845 tokens > 200000 maximum"` +- Verify `message()` returns the responseBody, not `"undefined"` +- Verify `isOverflow()` correctly detects the overflow pattern from the responseBody + +--- + +## Issue #3: `task-swallows-errors` — task.ts Silently Swallows Subagent Failures + +### Priority: P0 — Stop The Bleeding + +### What is the issue? +When a subagent (child session spawned by the `task` tool) fails with an error, the parent session shows it as **successfully completed with empty output**. The user sees a green ✅ checkmark for a task that actually errored. This is THE primary cause of "failures not reflected in main chat." + +### What is the bug? +`packages/opencode/src/tool/task.ts` line ~145: +```typescript +const result = await SessionPrompt.prompt({...}) +const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" +``` + +`result.info` contains an `.error` field when the child session errored (set by `processor.ts` at `input.assistantMessage.error = error`). But `task.ts` **never checks `result.info.error`** — it only looks for text parts. When the child errored, there are no text parts, so `text = ""`, and the parent receives `\n\n` as a "successful" empty result. + +### Where it can happen? +- Any subagent failure: context overflow, API error, tool execution error, rate limit +- Parallel subagents: if 1 of 3 subagents fails, parent sees 3 "completed" tasks with one having empty output +- The parent LLM may then hallucinate that the task completed or silently move on + +### What any agent needs to look for? +``` +File: packages/opencode/src/tool/task.ts +Location: After the `SessionPrompt.prompt()` call, before building the output +Pattern: result.info should have an error field — check result.info type definition +Also check: packages/opencode/src/session/prompt.ts for the return type of prompt() +``` + +### How to make the fix? +Add an error check immediately after the `SessionPrompt.prompt()` call: + +```typescript +const result = await SessionPrompt.prompt({...}) + +// Check if child session errored +if (result.info.error) { + const error = result.info.error + const msg = error.message ?? error.name ?? "Subagent task failed" + return { + title: params.description, + metadata: { sessionId: session.id, model }, + output: [ + `task_id: ${session.id}`, + "", + "", + `ERROR: ${msg}`, + `The subtask encountered an error and could not complete.`, + "", + ].join("\n"), + } +} + +const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" +``` + +**Important**: Check the actual type of `result.info` to use proper typing instead of `(result.info as any).error`. Look at how `processor.ts` sets the error on `input.assistantMessage.error` to understand the shape. + +### Testing +- Trigger a subagent error (e.g., invalid tool call, context overflow) +- Verify the parent session shows "ERROR: ..." in the task result, not empty +- Verify the parent LLM receives the error and can report it to the user + +--- + +## Issue #4: `bedrock-context-cap` — Bedrock Provider Missing Context Limit Override + +### Priority: P0 — This Sprint + +### What is the issue? +The `models-snapshot.ts` file (auto-generated from models.dev) lists Claude Opus 4.6 on Bedrock with `context: 1,000,000`. This is the model's capability WITH the `context-1m` beta header. However, the Bedrock provider handler in `provider.ts` **never sends the 1M beta header**, so Bedrock actually enforces a 200K limit. The result: UI shows "20% context usage" when the user is actually at 100% of the real limit, and compaction never triggers. + +### What is the bug? +Two bugs combine: + +1. **`models-snapshot.ts`** lists Opus 4.6 Bedrock models at 1M context (reflects model capability, not runtime limit) +2. **`provider.ts`** `"amazon-bedrock"` handler has NO logic to: + - Send `additionalModelRequestFields: { anthropic_beta: ["context-1m-2025-08-07"] }` to enable 1M + - Override the context limit to 200K when 1M beta is NOT active + +**Affected models in snapshot**: +``` +amazon-bedrock / anthropic.claude-opus-4-6-v1: context=1,000,000 ❌ +amazon-bedrock / us.anthropic.claude-opus-4-6-v1: context=1,000,000 ❌ +amazon-bedrock / eu.anthropic.claude-opus-4-6-v1: context=1,000,000 ❌ +amazon-bedrock / global.anthropic.claude-opus-4-6-v1: context=1,000,000 ❌ +``` + +All other Bedrock Claude models correctly show 200K. + +### Where it can happen? +- Any user running Claude Opus 4.6 via Amazon Bedrock +- Compaction threshold is calculated from `model.limit.context` → 1M → threshold ~900K +- Bedrock rejects at 200K → 700K token gap where compaction never fires but API always rejects +- Combined with Issue #1 (infinite retries), this causes the 3.5-hour freeze + +### What any agent needs to look for? +``` +File: packages/opencode/src/provider/provider.ts +Location: The "amazon-bedrock" entry in CUSTOM_LOADERS (~line 211) +Pattern: The returned object has options (providerOptions) and getModel() but NO context limit override +Also: Look at how compaction.ts uses model.limit.context (~line 33) +Also: Look at how Cline handles this — they use additionalModelRequestFields for Bedrock + +DO NOT edit models-snapshot.ts directly — it is auto-generated by build.ts +``` + +### How to make the fix? +**Option A (Recommended)**: Add provider-level context limit override in the model resolution logic. When provider is "amazon-bedrock" and model is Claude, cap context at 200K unless a 1M configuration is explicitly enabled. + +Look at where models are resolved and limits are applied. The fix should go in `provider.ts` where models are loaded/resolved, adding a context limit override: + +```typescript +// Inside amazon-bedrock handler or model resolution +if (providerID === "amazon-bedrock" && modelData.limit?.context > 200000) { + // Cap at 200K unless 1M beta is explicitly configured + modelData.limit.context = 200000 +} +``` + +**Option B (Future)**: Implement Cline's `:1m` suffix pattern — user explicitly opts into 1M context, which triggers adding `anthropic_beta: ["context-1m-2025-08-07"]` via `additionalModelRequestFields`. + +### Testing +- Configure Bedrock with Opus 4.6 +- Verify UI shows context limit as 200K (not 1M) +- Verify compaction triggers before hitting Bedrock's actual 200K limit +- Verify no "prompt is too long" errors during normal usage + +--- + +## Issue #5: `subagent-timeout` — task.ts Has No Execution Timeout + +### Priority: P0 — This Sprint + +### What is the issue? +The `task` tool calls `SessionPrompt.prompt()` with **no timeout or deadline**. If a subagent gets stuck (infinite retry storm, permission hang, or any other blocking issue), the parent tool call never resolves. The parent session appears frozen with a spinning "running" indicator forever. + +### What is the bug? +`packages/opencode/src/tool/task.ts`: +```typescript +const result = await SessionPrompt.prompt({ + messageID, + sessionID: session.id, + model: { modelID: model.modelID, providerID: model.providerID }, + agent: agent.name, + tools: { ... }, + parts: promptParts, +}) +// ← No timeout wrapper, no AbortController deadline +``` + +This Promise can hang indefinitely if the child session encounters: +- Infinite retry loop (Issue #1 before fix) +- Permission hang (Issue #6) +- Slow API responses that never complete + +### Where it can happen? +- Any subagent execution, but especially: + - When subagent hits context overflow with retries + - When subagent needs permission and user is watching parent + - When API provider is slow or unresponsive + +### What any agent needs to look for? +``` +File: packages/opencode/src/tool/task.ts +Location: The SessionPrompt.prompt() call +Pattern: Check if there's an AbortSignal or timeout mechanism available +Also check: How the abort signal flows from processor.ts → tool execution → task.ts +Also check: ctx parameter in execute() — does it carry an abort signal? +``` + +### How to make the fix? +Wrap the `SessionPrompt.prompt()` call with an AbortController timeout: + +```typescript +const timeout = 5 * 60 * 1000 // 5 minutes (configurable) +const controller = new AbortController() +const timer = setTimeout(() => controller.abort(), timeout) + +try { + const result = await SessionPrompt.prompt({ + // ... existing params ... + abort: controller.signal, // Pass abort signal if prompt() supports it + }) + clearTimeout(timer) + // ... process result ... +} catch (e) { + clearTimeout(timer) + if (controller.signal.aborted) { + return { + title: params.description, + metadata: { sessionId: session.id, model }, + output: `ERROR: Subtask timed out after ${timeout / 1000}s. The task may still be running in session ${session.id}.`, + } + } + throw e +} +``` + +Check if `SessionPrompt.prompt()` already accepts an `abort` parameter. If not, trace how `processor.ts` passes its abort signal and ensure the plumbing exists. + +### Testing +- Trigger a subagent that would hang (e.g., long-running task) +- Verify it times out after the configured deadline +- Verify the parent receives a timeout error message, not silent hang +- Verify the child session is properly cleaned up + +--- + +## Issue #6: `permission-abort` — next.ts Permission Promises Hang Forever in Subagents + +### Priority: P0 — This Sprint + +### What is the issue? +When a subagent's tool requires permission (e.g., file write, command execution), the permission prompt appears **only in the child session**. If the user is watching the parent session, they never see the prompt. The child session hangs forever waiting for permission, which blocks the parent's tool call. + +### What is the bug? +`packages/opencode/src/permission/next.ts` lines ~143-156: +```typescript +export function ask(input: AskInput) { + return new Promise((resolve, reject) => { + // ... sets up permission request ... + // NO abort signal listener + // NO timeout + // Promise resolves only when user explicitly grants/denies + }) +} +``` + +`grep -c "abort" next.ts` returns **0** — there is zero abort signal awareness in the entire file. + +### Where it can happen? +- Any subagent tool call that requires permission +- Parallel subagents: one hangs on permission → parent hangs → all other parallel results blocked +- Even with auto-approve policies, edge cases (new tools, destructive operations) may still prompt + +### What any agent needs to look for? +``` +File: packages/opencode/src/permission/next.ts +Location: The ask() function (exported, ~line 143) +Pattern: The Promise constructor — no abort/timeout handling +Also check: How ask() is called from tool execution context +Also check: Whether an AbortSignal is available in the call chain +Also check: packages/opencode/src/session/prompt.ts for where permissions are requested +``` + +### How to make the fix? +Add AbortSignal support to the `ask()` function: + +```typescript +export function ask(input: AskInput & { abort?: AbortSignal }) { + return new Promise((resolve, reject) => { + // Check if already aborted + if (input.abort?.aborted) { + return reject(new Error("Permission request aborted")) + } + + // Listen for abort + const onAbort = () => { + reject(new Error("Permission request aborted")) + } + input.abort?.addEventListener("abort", onAbort, { once: true }) + + // ... existing permission logic ... + // Clean up abort listener in resolve/reject paths + }) +} +``` + +**Important**: The abort signal must be plumbed from `processor.ts` through the tool execution chain to `next.ts`. Trace the call path: +``` +processor.ts (has abort) → tool execution → specific tool → permission check → next.ts ask() +``` + +### Testing +- Trigger a subagent that needs permission +- Abort the parent session while permission is pending +- Verify the child permission promise rejects +- Verify the parent tool call resolves with an error (not hangs forever) + +--- + +## Issue #7: `retry-catch-all` — retry.ts Catch-All Makes All JSON Errors Retryable + +### Priority: P1 — Robustness + +### What is the issue? +The `retryable()` function in `retry.ts` has a catch-all at line ~96 that makes **any error with a parseable JSON response body** retryable. This means Bedrock 400 errors ("prompt is too long"), which should NOT be retried, get classified as retryable — fueling the infinite retry storm. + +### What is the bug? +`packages/opencode/src/session/retry.ts` line ~96: +```typescript +// After checking specific patterns (rate limit, overloaded, etc.)... +return JSON.stringify(json) // ← ANY remaining JSON error = retryable +``` + +The Bedrock "prompt is too long" error response is valid JSON with `"isRetryable": false` in the body, but the catch-all ignores this field and returns the body as a retryable error message. + +### Where it can happen? +- Any API error that returns a JSON response body +- Specifically: Bedrock validation errors (400), authentication errors, quota errors +- Combined with Issue #1 (no max retries), this creates infinite retry storms + +### What any agent needs to look for? +``` +File: packages/opencode/src/session/retry.ts +Location: The retryable() function, specifically the catch-all after all pattern checks +Pattern: The final `return JSON.stringify(json)` that runs for any unmatched JSON error +Also check: What specific patterns ARE checked before the catch-all +Also check: Whether the JSON body contains "isRetryable" or HTTP status fields +``` + +### How to make the fix? +Replace the blanket catch-all with HTTP status-aware classification: + +```typescript +// Instead of: return JSON.stringify(json) +// Use: +const status = (json as any).status ?? (json as any).statusCode +if (typeof status === "number" && status >= 400 && status < 500) { + // 4xx errors are client errors — NOT retryable (bad request, auth, not found, etc.) + return undefined +} +// 5xx and truly unknown → retryable (but capped by MAX_RETRIES from Issue #1) +return JSON.stringify(json) +``` + +Also check for the `isRetryable` field that Bedrock includes: +```typescript +if ((json as any).isRetryable === false) return undefined +``` + +**Note**: This fix is SAFER when combined with Issue #1 (MAX_RETRIES), since any misclassification is bounded by the retry cap. + +### Testing +- Send a Bedrock 400 "prompt is too long" error → verify NOT retried +- Send a 429 rate limit error → verify IS retried +- Send a 500 server error → verify IS retried (up to MAX_RETRIES) +- Send a JSON error with `isRetryable: false` → verify NOT retried + +--- + +## Issue #8: `tool-error-metadata` — processor.ts Drops Metadata on Tool Errors + +### Priority: P1 — Robustness + +### What is the issue? +When a tool execution errors, the tool-error handler in `processor.ts` rebuilds the tool state but **drops the `title` and `metadata` fields**. This means the UI loses the tool's display name and any navigation metadata (like `sessionId` for subagent links). + +### What is the bug? +`packages/opencode/src/session/processor.ts` lines ~207-218, the `"tool-error"` case: +```typescript +case "tool-error": { + const match = toolcalls[value.toolCallId] + if (match && match.state.status === "running") { + await Session.updatePart({ + ...match, + state: { + status: "error", + input: value.input ?? match.state.input, + error: (value.error as any).toString(), + // ❌ Missing: title: match.state.title, + // ❌ Missing: metadata: match.state.metadata, + time: { + start: match.state.time.start, + end: Date.now(), + }, + }, + }) + } +} +``` + +### Where it can happen? +- Any tool that errors during execution +- Most visible for task tool errors — the `sessionId` metadata (used for navigating to child sessions) is lost +- Also affects batch tool parts and any tool with custom title/metadata + +### What any agent needs to look for? +``` +File: packages/opencode/src/session/processor.ts +Location: The "tool-error" case in the stream event handler +Pattern: Compare the "tool-error" state update with the "tool-result" state update +The "tool-result" case preserves title and metadata, but "tool-error" does not +``` + +### How to make the fix? +Add `title` and `metadata` preservation to the error state: + +```typescript +case "tool-error": { + const match = toolcalls[value.toolCallId] + if (match && match.state.status === "running") { + await Session.updatePart({ + ...match, + state: { + status: "error", + input: value.input ?? match.state.input, + error: (value.error as any).toString(), + title: match.state.title, // ← ADD + metadata: match.state.metadata, // ← ADD + time: { + start: match.state.time.start, + end: Date.now(), + }, + }, + }) + } +} +``` + +### Testing +- Trigger a tool error (e.g., file read on non-existent path) +- Verify the error part in the UI shows the tool title +- Trigger a subagent error → verify the sessionId metadata is preserved in the error part + +--- + +## Issue #9: `batch-error-details` — batch.ts Output Lacks Per-Tool Error Details + +### Priority: P2 — Nice to Have + +### What is the issue? +When batch tool calls fail, the output summary only says `"Executed X/Y tools successfully. Z failed."` without including **which tools failed or why**. The LLM receiving this output cannot diagnose or intelligently retry the failures. + +### What is the bug? +`packages/opencode/src/tool/batch.ts` output message: +```typescript +const outputMessage = failedCalls > 0 + ? `Executed ${successfulCalls}/${results.length} tools successfully. ${failedCalls} failed.` + : `All ${successfulCalls} tools executed successfully.` +``` + +Note: Individual tool-call parts ARE written to the database with their errors (via `Session.updatePart` in the catch block), so the UI shows them. But the **summary message returned to the LLM** lacks details. + +### Where it can happen? +- Any batch execution where one or more tools fail +- The LLM sees the summary but not the individual error details +- Can cause the LLM to blindly retry the same failing operations + +### What any agent needs to look for? +``` +File: packages/opencode/src/tool/batch.ts +Location: The outputMessage construction after Promise.all results +Pattern: The results array has { success, tool, error? } for each call +``` + +### How to make the fix? +Include per-tool error details in the output: + +```typescript +const outputMessage = failedCalls > 0 + ? [ + `Executed ${successfulCalls}/${results.length} tools successfully. ${failedCalls} failed.`, + "", + "Failed tools:", + ...results + .filter((r) => !r.success) + .map((r) => `- ${r.tool}: ${r.error instanceof Error ? r.error.message : String(r.error)}`), + ].join("\n") + : `All ${successfulCalls} tools executed successfully.\n\nKeep using the batch tool for optimal performance in your next response!` +``` + +### Testing +- Execute a batch with one intentionally failing tool (e.g., read non-existent file) +- Verify the output includes the tool name and error message +- Verify the LLM can see which tool failed and why + +--- + +## Implementation Order + +``` +✅ DONE — Commit 3670d5f2f: + #1 processor-max-retries → MAX_RETRIES=10 cap + #2 bedrock-undefined-message → "undefined" → responseBody fallback + #3 task-swallows-errors → result.info.error check in task.ts + #8 tool-error-metadata → metadata preserved on tool-error + +✅ DONE — Commit a8758b20f: + #4 bedrock-context-cap → 200K cap in both fromModelsDevModel + config path + #7 retry-catch-all → isRetryable:false + 4xx status guards + #9 batch-error-details → per-tool error details in output + +REMAINING (P0 — Needs deep plumbing): + #5 subagent-timeout → Hung subagent prevention + #6 permission-abort → Permission hang prevention +``` diff --git a/docs/09-temp/ui-overhaul-plan.md b/docs/09-temp/ui-overhaul-plan.md new file mode 100644 index 000000000000..70d205acaf1a --- /dev/null +++ b/docs/09-temp/ui-overhaul-plan.md @@ -0,0 +1,94 @@ +# UI/UX Overhaul Plan — OpenCode Desktop + +**Date:** 2026-02-24 +**Status:** ✅ PHASE 1 COMPLETE + +## User Requirements +- UI looks "very bad" — needs visual polish and tactile feel ✅ +- More themes and theme customization ✅ +- Better UI rendering quality ✅ +- Font size ✅ (fixed in PR #14821) +- Zoom in/out ✅ (already works via Cmd+/-/0) +- Wide mode ✅ (added in PR #14835) +- More UI settings options needed (future) + +## Phase 1 Changes (Completed) + +### 1. Font Rendering (`base.css`) +- Added `-webkit-font-smoothing: antialiased` for crisp text on macOS +- Added `-moz-osx-font-smoothing: grayscale` for Firefox +- Added `text-rendering: optimizeLegibility` for better kerning +- Added `scroll-behavior: smooth` for smooth scrolling + +### 2. Animation System (`animations.css`) +- Added CSS custom property easing tokens (`--ease-out-expo`, `--ease-spring`, etc.) +- Added duration tokens (`--duration-instant` through `--duration-slower`) +- Added new keyframes: `fadeIn`, `fadeInScale`, `slideInFromRight/Left/Bottom` +- Added `subtleGlow` for focus states, `shimmer` for loading, `spin` +- Halved stagger delay (50ms instead of 100ms) for snappier text reveals +- Added `prefers-reduced-motion: reduce` media query for accessibility + +### 3. Utilities (`utilities.css`) +- Added `::selection` styling with theme-aware color +- Added global transition defaults for all interactive elements +- Added `:focus-visible` ring with theme color +- Added thin scrollbar styling for scroll views +- Suppressed focus ring for components that handle their own + +### 4. Shadow/Depth System (`theme.css`) +- Refined `--shadow-xs` with slightly stronger presence +- Added new `--shadow-sm` level for subtle elevation +- Enhanced `--shadow-md` with deeper, more dramatic depth +- Enhanced `--shadow-lg` with softer, more premium feel +- Added new `--shadow-xl` for maximum elevation (modals, floating panels) + +### 5. Button Micro-Interactions (`button.css`) +- Added explicit transition for bg-color, border, box-shadow, transform, opacity +- Primary: hover now lifts with `--shadow-sm`, active presses with `scale(0.98)` +- Ghost: icon color transitions on hover, active presses with `scale(0.97)` +- Secondary: hover adds border shadow hint, active presses +- Disabled states now use `opacity: 0.6` for clearer visual feedback + +### 6. Card Polish (`card.css`) +- Upgraded border-radius from `--radius-md` to `--radius-lg` +- Added full transition for bg-color, border-color, box-shadow, transform +- Hover state now shows subtle border highlight and `--shadow-xs` elevation + +### 7. Dialog Animations (`dialog.css`) +- Overlay now uses `backdrop-filter: blur(4px)` for frosted glass effect +- Overlay opacity increased from 0.2 to 0.35 for better focus +- Content now uses combined `scale(0.96) + translateY(4px)` entrance +- Animation uses `cubic-bezier(0.16, 1, 0.3, 1)` expo-out for premium feel +- Added subtle 1px border ring on dialog content for depth definition +- Overlay entrance/exit now animated separately + +### 8. Icon Button Interactions (`icon-button.css`) +- Added explicit transitions for bg-color, box-shadow, transform +- Ghost variant: icon color now transitions on hover (to `--icon-hover`) +- Active state now scales to `0.92` for satisfying tactile press +- Icon SVG color now properly transitions through states +- Disabled state uses `opacity: 0.5` + +### 9. New Themes (3 premium additions) +- **Rosé Pine** — Dreamy, soft palette with purple/rose accents. Very popular community theme. +- **Kanagawa** — Japanese-inspired warm palette. Distinctive golden/purple tones based on "The Great Wave." +- **Everforest** — Calming green/earth tones nature-inspired palette. Easy on the eyes for long sessions. + +All themes include full light + dark variants with seeds, borders, surfaces, text, syntax highlighting, and markdown colors. + +## Files Modified +- `packages/ui/src/styles/base.css` — Font rendering +- `packages/ui/src/styles/animations.css` — Animation system +- `packages/ui/src/styles/utilities.css` — Selection, focus, transitions, scrollbars +- `packages/ui/src/styles/theme.css` — Shadow system +- `packages/ui/src/components/button.css` — Button interactions +- `packages/ui/src/components/card.css` — Card polish +- `packages/ui/src/components/dialog.css` — Dialog animations +- `packages/ui/src/components/icon-button.css` — Icon button interactions +- `packages/ui/src/theme/themes/rosepine.json` — NEW +- `packages/ui/src/theme/themes/kanagawa.json` — NEW +- `packages/ui/src/theme/themes/everforest.json` — NEW +- `packages/ui/src/theme/default-themes.ts` — Theme registration + +## Build Status +✅ `vite build` passes with zero errors (7.98s) diff --git a/docs/09-temp/ui-redesign-spec.md b/docs/09-temp/ui-redesign-spec.md new file mode 100644 index 000000000000..718b5ee2ecfb --- /dev/null +++ b/docs/09-temp/ui-redesign-spec.md @@ -0,0 +1,37 @@ +# UI Redesign Spec + +## Reference Design +See HTML mockup provided by user. Key elements: + +### Sidebar +- "New Session" button with icon, primary color border +- "RECENT CHATS" section header (uppercase, tracking-wider) +- Chat items with icon + title + timestamp +- "CONTEXT" section with file list +- Bottom: plan usage bar + +### Message Timeline +- Assistant: Robot icon (32x32 rounded square) + "OPENCODE AI" label (uppercase, primary color, bold) +- User: Timestamp + "You" label (accent-cyan color, bold) +- User message: Glass panel, rounded-2xl with rounded-tr-none + +### Thinking Block +- Collapsible `
` with: + - Cyan pulsing dot + "Thinking process..." text + - Expand/collapse arrow + - Mono font content with `>` prefix + - Border-top separator + +### Prompt Input +- Glass panel with backdrop-blur +- Model selector pills ("GPT-4o", "Web Search") +- Textarea +- Send button with primary color + glow shadow +- Bottom bar: keyboard shortcuts + sync status + +### Right Activity Bar +- Vertical icon strip: Extensions, Source Control, History +- Bottom: Settings + user avatar + +### Settings (from screenshot) +- Already looks reasonable, minor polish needed diff --git a/docs/plans/2025-02-26-aurora-design-system.md b/docs/plans/2025-02-26-aurora-design-system.md new file mode 100644 index 000000000000..9dd67f391618 --- /dev/null +++ b/docs/plans/2025-02-26-aurora-design-system.md @@ -0,0 +1,1977 @@ +# 🌌 Aurora Design System for opencode + +> **Vision**: "Code illuminated from within" +> +> A unified design language for opencode that creates an ethereal, digital-native interface where UI elements emit light rather than receive it. + +--- + +## Table of Contents + +1. [Design Vision Summary](#part-1-design-vision-summary) +2. [Design Approaches Explored](#part-2-design-approaches-explored) +3. [Color System](#part-3-color-system) +4. [Typography System](#part-4-typography-system) +5. [Spacing System](#part-5-spacing-system) +6. [Motion & Animation System](#part-6-motion--animation-system) +7. [Component Specifications](#part-7-component-specifications) +8. [TUI (Terminal) Component Translations](#part-8-tui-terminal-component-translations) +9. [Stitch Prompts for Visual Prototyping](#part-9-stitch-prompts-for-visual-prototyping) +10. [Final Summary & Implementation Guide](#part-10-final-summary--implementation-guide) +11. [Accessibility & Review Amendments](#part-11-accessibility--review-amendments) + +--- + +## Part 1: Design Vision Summary + +### Design Requirements Gathered + +| Aspect | Choice | +|--------|--------| +| **Scope** | Unified design language (Web Console + Terminal UI) | +| **Tone** | Luxury Minimal | +| **Color** | Dark-first luxury with luminous accents | +| **Motion** | Confident, tactile, functional | +| **Reference** | Future-forward (Tesla/Rivian interiors) | + +### Core Identity + +``` +┌─────────────────────────────────────────────────────────────┐ +│ │ +│ opencode AURORA │ +│ │ +│ "Code illuminated from within" │ +│ │ +│ Not a tool that shows you code— │ +│ A window into a dimension where code IS light. │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Design Principles + +| Principle | Description | Implementation | +|-----------|-------------|----------------| +| **Light as Material** | UI elements emit light rather than receive it | Glows, gradients, luminous borders | +| **Depth through Transparency** | Layers visible through glassmorphism | backdrop-blur, low-opacity backgrounds | +| **Confident Motion** | Every animation serves purpose and feels physical | Spring physics, 200-300ms durations | +| **Chromatic Restraint** | Rich palette but used sparingly | Monochrome base, color for meaning | +| **Unified Language** | Same DNA across Web and TUI | Shared color tokens, adapted to medium | + +--- + +## Part 2: Design Approaches Explored + +Three design approaches were explored before settling on Aurora: + +### Approach A: "Carbon Fiber" — Industrial Luxury (Rejected) + +**Concept:** Premium materials meet precision engineering. Think machined aluminum bezels, carbon fiber textures, and surgical-grade steel accents. + +**Web Console:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ +│ Background: Subtle carbon weave pattern with depth │ +│ Cards: Brushed metal finish with soft inner glow │ +│ Accent: Copper/rose gold highlights (warm against cold) │ +│ │ +│ 3D Elements: │ +│ • Cards tilt on hover (perspective transform) │ +│ • Depth shadows that respond to mouse position │ +│ • Metallic sheen that catches virtual "light" │ +│ │ +│ Motion: │ +│ • Spring-based button depressions (like mechanical keys) │ +│ • Smooth state transitions with mass/velocity physics │ +│ • Loading: Rotating machined bezel indicator │ +└─────────────────────────────────────────────────────────────┘ +``` + +**TUI Translation:** +``` +┌─ SESSION: Project Analysis ─────────────── ◈ ────┐ +│ │ +│ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░ Processing... │ +│ │ +│ ╭──────────────────────────────────────────╮ │ +│ │ ◆ Analyzing codebase │ │ +│ │ └─ Found 127 TypeScript files │ │ +│ │ └─ Detected SolidJS framework │ │ +│ ╰──────────────────────────────────────────╯ │ +│ │ +│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ +│ Unicode: Heavy borders, diamond bullets │ +│ Colors: Warm copper (#B87333) on charcoal │ +└───────────────────────────────────────────────────┘ +``` + +**Pros:** +- Distinctive, memorable aesthetic +- Strong brand identity ("the tool that feels engineered") +- Warm accent prevents cold/sterile feeling + +**Cons:** +- Carbon texture could feel dated if not executed perfectly +- Copper might clash with some terminal color schemes +- More complex to implement subtle material effects + +**Verdict:** Too industrial - user wanted something more ethereal/digital, less physical materials + +--- + +### Approach B: "Aurora" — Digital Luminescence (SELECTED ✓) + +**Concept:** Pure light and energy. No physical materials—just gradients, glows, and luminous color that feels alive. Like looking at code through a prism of pure digital light. + +**Web Console:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ │ +│ Background: Deep void black (#0A0A0F) with subtle │ +│ animated gradient aurora (very slow drift) │ +│ │ +│ Cards: Glassmorphism with luminous edge glow │ +│ bg: rgba(255,255,255,0.03) │ +│ border: gradient (cyan → purple → magenta) │ +│ backdrop-filter: blur(20px) │ +│ │ +│ Accent Colors (shifting spectrum): │ +│ • Primary: Electric Cyan (#00D4FF) │ +│ • Secondary: Soft Violet (#A78BFA) │ +│ • Tertiary: Rose (#FF6B9D) │ +│ │ +│ 3D Elements: │ +│ • Subtle glow pulses (like breathing light) │ +│ • Hover reveals inner luminescence │ +│ • Focus states bloom with soft radiance │ +│ • Depth through layered transparency, not shadows │ +│ │ +│ Motion: │ +│ • Smooth spring physics on all interactions │ +│ • Elements "float" with subtle parallax │ +│ • Loading: Gradient shimmer / aurora wave │ +│ • Transitions: Fade + scale with glow trail │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Light Theme Variant:** +``` +┌─────────────────────────────────────────────────────────────┐ +│ Background: Soft pearl (#FAFAFA) with subtle iridescence │ +│ Cards: Frosted glass with prismatic edge highlights │ +│ Accents: Deeper cyan, rich violet (contrast preserved) │ +│ Effect: "Daylight aurora" - colors visible but softer │ +└─────────────────────────────────────────────────────────────┘ +``` + +**TUI Translation:** +``` +╭───────────────────────────────────────────────────────╮ +│ ● opencode ◐ processing │ +├───────────────────────────────────────────────────────┤ +│ │ +│ ┃ Analyzing your codebase... │ +│ ┃ │ +│ ├─● packages/opencode/src/cli/ │ +│ │ ├─○ cmd/tui/app.tsx │ +│ │ ├─○ cmd/tui/context/theme.tsx │ +│ │ └─● cmd/tui/routes/session/ │ +│ │ └─○ index.tsx ← focus │ +│ │ │ +│ ╰─ Found 247 files in 3.2s │ +│ │ +│ ░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ +│ │ +╰───────────────────────────────────────────────────────╯ +│ Unicode: Rounded corners, thin lines, ● ○ bullets │ +│ Colors: Cyan/violet/magenta gradient hierarchy │ +│ Effect: "Glowing" text via bright-on-dark contrast │ +└───────────────────────────────────────────────────────┘ +``` + +**Key Differentiators:** +- **Depth through light, not shadow** — Elements glow from within rather than casting shadows +- **Living gradients** — Subtle color shifts that feel organic, not static +- **Ethereal presence** — UI feels like it exists in digital space, weightless + +**Pros:** +- Truly unique aesthetic (few tools look like this) +- Perfectly digital - no physical material metaphors +- Light/dark themes can share the same luminous DNA +- Scalable: subtle for everyday use, dramatic for hero moments + +**Cons:** +- Risk of "gaming aesthetic" if not carefully restrained +- Gradient animations need to be VERY subtle or becomes distracting +- Performance consideration for animated gradients + +**Verdict:** Selected as final direction - ethereal, digital, distinctive + +--- + +### Approach C: Not Developed + +Since Approach B (Aurora) was selected immediately, a third approach was not fully developed. + +--- + +## Part 3: Color System + +### Dark Theme (Primary) + +```css +/* ═══════════════════════════════════════════════════════════ + AURORA DARK — PRIMARY THEME + ═══════════════════════════════════════════════════════════ */ + +:root[data-theme="aurora-dark"] { + /* ─── VOID BACKGROUNDS ─── */ + --void-deepest: #050508; /* True dark, almost black */ + --void-deep: #0A0A0F; /* Primary background */ + --void-base: #0F0F14; /* Card backgrounds */ + --void-elevated: #14141A; /* Elevated surfaces */ + --void-hover: #1A1A22; /* Hover states */ + + /* ─── SURFACE GLASS ─── */ + --glass-subtle: rgba(255, 255, 255, 0.02); + --glass-light: rgba(255, 255, 255, 0.04); + --glass-medium: rgba(255, 255, 255, 0.06); + --glass-strong: rgba(255, 255, 255, 0.08); + + /* ─── LUMINOUS SPECTRUM ─── */ + --aurora-cyan: #00D4FF; /* Primary accent */ + --aurora-cyan-soft: #00A3CC; /* Cyan muted */ + --aurora-cyan-glow: rgba(0, 212, 255, 0.15); + + --aurora-violet: #A78BFA; /* Secondary accent */ + --aurora-violet-soft:#8B6ED9; + --aurora-violet-glow:rgba(167, 139, 250, 0.15); + + --aurora-rose: #FF6B9D; /* Tertiary / attention */ + --aurora-rose-soft: #D94A7B; + --aurora-rose-glow: rgba(255, 107, 157, 0.15); + + --aurora-amber: #FFBB33; /* Warning / warm accent */ + --aurora-green: #4ADE80; /* Success */ + --aurora-red: #F87171; /* Error / danger */ + + /* ─── TEXT HIERARCHY ─── */ + --text-primary: #F5F5F7; /* Bright white */ + --text-secondary: #A1A1AA; /* Muted gray */ + --text-tertiary: #71717A; /* Subtle gray */ + --text-disabled: #3F3F46; /* Very dim */ + + /* ─── BORDER LUMINANCE ─── */ + --border-subtle: rgba(255, 255, 255, 0.06); + --border-default: rgba(255, 255, 255, 0.10); + --border-strong: rgba(255, 255, 255, 0.15); + --border-glow: var(--aurora-cyan); +} +``` + +### Light Theme (Secondary) + +```css +/* ═══════════════════════════════════════════════════════════ + AURORA LIGHT — DAYLIGHT VARIANT + ═══════════════════════════════════════════════════════════ */ + +:root[data-theme="aurora-light"] { + /* ─── PEARL BACKGROUNDS ─── */ + --void-deepest: #FFFFFF; + --void-deep: #FAFAFA; + --void-base: #F4F4F5; + --void-elevated: #FFFFFF; + --void-hover: #E4E4E7; + + /* ─── SURFACE FROST ─── */ + --glass-subtle: rgba(0, 0, 0, 0.02); + --glass-light: rgba(0, 0, 0, 0.04); + --glass-medium: rgba(0, 0, 0, 0.06); + --glass-strong: rgba(0, 0, 0, 0.08); + + /* ─── LUMINOUS SPECTRUM (deeper for contrast) ─── */ + --aurora-cyan: #0891B2; /* Deeper cyan */ + --aurora-cyan-soft: #06B6D4; + --aurora-cyan-glow: rgba(8, 145, 178, 0.10); + + --aurora-violet: #7C3AED; /* Richer violet */ + --aurora-violet-soft:#8B5CF6; + --aurora-violet-glow:rgba(124, 58, 237, 0.10); + + --aurora-rose: #DB2777; /* Deeper rose */ + --aurora-rose-soft: #EC4899; + --aurora-rose-glow: rgba(219, 39, 119, 0.10); + + --aurora-amber: #D97706; + --aurora-green: #16A34A; + --aurora-red: #DC2626; + + /* ─── TEXT HIERARCHY ─── */ + --text-primary: #18181B; + --text-secondary: #52525B; + --text-tertiary: #A1A1AA; + --text-disabled: #D4D4D8; + + /* ─── BORDER LUMINANCE ─── */ + --border-subtle: rgba(0, 0, 0, 0.06); + --border-default: rgba(0, 0, 0, 0.10); + --border-strong: rgba(0, 0, 0, 0.15); + --border-glow: var(--aurora-cyan); +} +``` + +### TUI Color Mapping + +```typescript +// Aurora theme for terminal (TUI) +export const auroraDark = { + // Backgrounds (mapped to closest ANSI/24-bit) + background: RGBA.fromHex("#0A0A0F"), + backgroundPanel: RGBA.fromHex("#0F0F14"), + backgroundElement: RGBA.fromHex("#14141A"), + backgroundMenu: RGBA.fromHex("#1A1A22"), + + // Aurora spectrum + primary: RGBA.fromHex("#00D4FF"), // Cyan + secondary: RGBA.fromHex("#A78BFA"), // Violet + accent: RGBA.fromHex("#FF6B9D"), // Rose + + // Semantic + success: RGBA.fromHex("#4ADE80"), + warning: RGBA.fromHex("#FFBB33"), + error: RGBA.fromHex("#F87171"), + info: RGBA.fromHex("#00D4FF"), + + // Text + text: RGBA.fromHex("#F5F5F7"), + textMuted: RGBA.fromHex("#A1A1AA"), + + // Borders + border: RGBA.fromHex("#1E1E26"), + borderActive: RGBA.fromHex("#00D4FF"), + borderSubtle: RGBA.fromHex("#14141A"), + + // Syntax highlighting (aurora-themed) + syntaxKeyword: RGBA.fromHex("#A78BFA"), // Violet + syntaxFunction: RGBA.fromHex("#00D4FF"), // Cyan + syntaxString: RGBA.fromHex("#4ADE80"), // Green + syntaxNumber: RGBA.fromHex("#FF6B9D"), // Rose + syntaxComment: RGBA.fromHex("#71717A"), // Muted + syntaxVariable: RGBA.fromHex("#F5F5F7"), // White + syntaxType: RGBA.fromHex("#FFBB33"), // Amber + syntaxOperator: RGBA.fromHex("#A1A1AA"), + syntaxPunctuation: RGBA.fromHex("#71717A"), + + // Diff colors + diffAdded: RGBA.fromHex("#4ADE80"), + diffRemoved: RGBA.fromHex("#F87171"), + diffAddedBg: RGBA.fromHex("#0D2818"), + diffRemovedBg: RGBA.fromHex("#2D1216"), +} +``` + +--- + +## Part 4: Typography System + +### Font Stack + +```css +/* ═══════════════════════════════════════════════════════════ + AURORA TYPOGRAPHY + ═══════════════════════════════════════════════════════════ */ + +:root { + /* ─── PRIMARY: Code & Interface ─── */ + --font-mono: "JetBrains Mono", "SF Mono", "Fira Code", + "Cascadia Code", monospace; + + /* ─── DISPLAY: Headers & Hero Text ─── */ + /* Option A: Geometric (Future-forward) */ + --font-display: "Geist", "Inter", "SF Pro Display", + system-ui, sans-serif; + + /* Option B: More distinctive (if we want stronger brand) */ + /* --font-display: "Space Grotesk", "Outfit", sans-serif; */ + + /* ─── BODY: Documentation & Long-form ─── */ + --font-body: "Inter", "SF Pro Text", system-ui, sans-serif; +} +``` + +### Type Scale + +```css +/* ─── MODULAR SCALE: 1.250 (Major Third) ─── */ + +:root { + --text-xs: 0.64rem; /* 10.24px - Labels, captions */ + --text-sm: 0.8rem; /* 12.8px - Small UI text */ + --text-base: 1rem; /* 16px - Body text */ + --text-md: 1.25rem; /* 20px - Large body */ + --text-lg: 1.563rem; /* 25px - Section headers */ + --text-xl: 1.953rem; /* 31.25px - Page headers */ + --text-2xl: 2.441rem; /* 39px - Hero subheads */ + --text-3xl: 3.052rem; /* 48.8px - Hero headlines */ + --text-4xl: 3.815rem; /* 61px - Display text */ + + /* ─── LINE HEIGHTS ─── */ + --leading-none: 1; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 1.75; + + /* ─── LETTER SPACING ─── */ + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + + /* ─── FONT WEIGHTS ─── */ + --weight-normal: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; +} +``` + +### Typography Classes + +```css +/* ─── SEMANTIC TEXT STYLES ─── */ + +.text-display-hero { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--weight-bold); + line-height: var(--leading-none); + letter-spacing: var(--tracking-tighter); +} + +.text-display-title { + font-family: var(--font-display); + font-size: var(--text-2xl); + font-weight: var(--weight-semibold); + line-height: var(--leading-tight); + letter-spacing: var(--tracking-tight); +} + +.text-heading-lg { + font-family: var(--font-display); + font-size: var(--text-xl); + font-weight: var(--weight-semibold); + line-height: var(--leading-snug); +} + +.text-heading-md { + font-family: var(--font-display); + font-size: var(--text-lg); + font-weight: var(--weight-medium); + line-height: var(--leading-snug); +} + +.text-body { + font-family: var(--font-body); + font-size: var(--text-base); + font-weight: var(--weight-normal); + line-height: var(--leading-relaxed); +} + +.text-body-sm { + font-family: var(--font-body); + font-size: var(--text-sm); + line-height: var(--leading-normal); +} + +.text-code { + font-family: var(--font-mono); + font-size: var(--text-sm); + line-height: var(--leading-normal); + font-variant-ligatures: contextual; /* Enable code ligatures */ +} + +.text-label { + font-family: var(--font-mono); + font-size: var(--text-xs); + font-weight: var(--weight-medium); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; +} +``` + +--- + +## Part 5: Spacing System + +```css +/* ═══════════════════════════════════════════════════════════ + AURORA SPACING — 4px Base Grid + ═══════════════════════════════════════════════════════════ */ + +:root { + --space-px: 1px; + --space-0: 0; + --space-0.5: 0.125rem; /* 2px */ + --space-1: 0.25rem; /* 4px */ + --space-1.5: 0.375rem; /* 6px */ + --space-2: 0.5rem; /* 8px */ + --space-2.5: 0.625rem; /* 10px */ + --space-3: 0.75rem; /* 12px */ + --space-3.5: 0.875rem; /* 14px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-7: 1.75rem; /* 28px */ + --space-8: 2rem; /* 32px */ + --space-9: 2.25rem; /* 36px */ + --space-10: 2.5rem; /* 40px */ + --space-11: 2.75rem; /* 44px */ + --space-12: 3rem; /* 48px */ + --space-14: 3.5rem; /* 56px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + --space-28: 7rem; /* 112px */ + --space-32: 8rem; /* 128px */ + + /* ─── SEMANTIC SPACING ─── */ + --gap-xs: var(--space-1); /* 4px - Inline elements */ + --gap-sm: var(--space-2); /* 8px - Tight groups */ + --gap-md: var(--space-4); /* 16px - Default gap */ + --gap-lg: var(--space-6); /* 24px - Section spacing */ + --gap-xl: var(--space-8); /* 32px - Major sections */ + --gap-2xl: var(--space-12); /* 48px - Page sections */ + + /* ─── COMPONENT PADDING ─── */ + --padding-button: var(--space-2) var(--space-4); + --padding-button-sm: var(--space-1.5) var(--space-3); + --padding-button-lg: var(--space-3) var(--space-6); + + --padding-card: var(--space-5); + --padding-card-sm: var(--space-3); + --padding-card-lg: var(--space-6); + + --padding-input: var(--space-2.5) var(--space-3); + + /* ─── BORDER RADIUS ─── */ + --radius-none: 0; + --radius-sm: 0.25rem; /* 4px - Small elements */ + --radius-md: 0.5rem; /* 8px - Buttons, inputs */ + --radius-lg: 0.75rem; /* 12px - Cards */ + --radius-xl: 1rem; /* 16px - Large cards */ + --radius-2xl: 1.5rem; /* 24px - Modals */ + --radius-full: 9999px; /* Pills, avatars */ +} +``` + +--- + +## Part 6: Motion & Animation System + +```css +/* ═══════════════════════════════════════════════════════════ + AURORA MOTION — Spring-Based Animation + ═══════════════════════════════════════════════════════════ */ + +:root { + /* ─── DURATION ─── */ + --duration-instant: 50ms; + --duration-fast: 150ms; + --duration-normal: 250ms; + --duration-slow: 350ms; + --duration-slower: 500ms; + --duration-slowest: 700ms; + + /* ─── EASING (CSS) ─── */ + --ease-linear: linear; + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + + /* ─── SPRING EASING (For Motion library) ─── */ + --spring-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); + --spring-smooth: cubic-bezier(0.22, 1, 0.36, 1); + --spring-snappy: cubic-bezier(0.16, 1, 0.3, 1); + + /* ─── SEMANTIC TRANSITIONS ─── */ + --transition-colors: color var(--duration-fast) var(--ease-out), + background-color var(--duration-fast) var(--ease-out), + border-color var(--duration-fast) var(--ease-out); + + --transition-opacity: opacity var(--duration-normal) var(--ease-out); + + --transition-transform: transform var(--duration-normal) var(--spring-smooth); + + --transition-all: all var(--duration-normal) var(--spring-smooth); + + --transition-glow: box-shadow var(--duration-slow) var(--ease-out); +} +``` + +### Motion Principles + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA MOTION PRINCIPLES │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 1. ENTER: Scale up + fade in (0.95 → 1.0, 0 → 1) │ +│ 2. EXIT: Scale down + fade out (1.0 → 0.95, 1 → 0) │ +│ 3. HOVER: Subtle lift (translateY -2px) + glow increase │ +│ 4. PRESS: Slight compression (scale 0.98) │ +│ 5. FOCUS: Glow ring expansion │ +│ │ +│ Key insight: Aurora elements GLOW more on interaction, │ +│ they don't cast shadows—they emit light. │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Animation Keyframes + +```css +/* ─── ENTRY ANIMATIONS ─── */ +@keyframes aurora-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes aurora-scale-in { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes aurora-slide-up { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ─── GLOW PULSE (for loading/processing) ─── */ +@keyframes aurora-pulse { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 0 0 var(--aurora-cyan-glow); + } + 50% { + opacity: 0.8; + box-shadow: 0 0 20px 4px var(--aurora-cyan-glow); + } +} + +/* ─── SHIMMER (for skeleton loaders) ─── */ +@keyframes aurora-shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +/* ─── GRADIENT DRIFT (for hero backgrounds) ─── */ +@keyframes aurora-drift { + 0%, 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} +``` + +--- + +## Part 7: Component Specifications + +### 7.1 Buttons + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA BUTTONS │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ PRIMARY (Glowing CTA) │ +│ ┌─────────────────────────────────────┐ │ +│ │ ◉ Start Session │ ← Cyan glow ring │ +│ └─────────────────────────────────────┘ │ +│ bg: var(--aurora-cyan) │ +│ text: var(--void-deepest) │ +│ hover: glow expands, brightness +10% │ +│ active: scale(0.98), glow contracts │ +│ │ +│ SECONDARY (Glass) │ +│ ┌─────────────────────────────────────┐ │ +│ │ View History │ ← Subtle border │ +│ └─────────────────────────────────────┘ │ +│ bg: var(--glass-light) │ +│ border: var(--border-default) │ +│ hover: bg → glass-medium, border glows │ +│ │ +│ GHOST (Minimal) │ +│ ┌─────────────────────────────────────┐ │ +│ │ Cancel │ ← No bg │ +│ └─────────────────────────────────────┘ │ +│ bg: transparent │ +│ hover: var(--glass-subtle) │ +│ │ +│ DANGER (Warning glow) │ +│ ┌─────────────────────────────────────┐ │ +│ │ Delete Session │ ← Red glow │ +│ └─────────────────────────────────────┘ │ +│ bg: var(--aurora-red) at 15% opacity │ +│ border: var(--aurora-red) │ +│ hover: red glow expands │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +```css +/* Primary Button */ +.btn-primary { + background: var(--aurora-cyan); + color: var(--void-deepest); + padding: var(--padding-button); + border-radius: var(--radius-md); + font-weight: var(--weight-medium); + transition: var(--transition-all); + box-shadow: + 0 0 0 0 var(--aurora-cyan-glow), + 0 0 20px -5px var(--aurora-cyan); +} + +.btn-primary:hover { + box-shadow: + 0 0 0 4px var(--aurora-cyan-glow), + 0 0 30px -5px var(--aurora-cyan); + filter: brightness(1.1); +} + +.btn-primary:active { + transform: scale(0.98); + box-shadow: + 0 0 0 2px var(--aurora-cyan-glow), + 0 0 15px -5px var(--aurora-cyan); +} + +/* Secondary Button */ +.btn-secondary { + background: var(--glass-light); + border: 1px solid var(--border-default); + color: var(--text-primary); + padding: var(--padding-button); + border-radius: var(--radius-md); + backdrop-filter: blur(8px); + transition: var(--transition-all); +} + +.btn-secondary:hover { + background: var(--glass-medium); + border-color: var(--aurora-cyan); + box-shadow: 0 0 15px -5px var(--aurora-cyan-glow); +} +``` + +--- + +### 7.2 Cards + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA CARDS │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ GLASS CARD (Default) │ +│ ╭───────────────────────────────────────────╮ │ +│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │ +│ │ │ │ +│ │ Session Title │ │ +│ │ Subtitle or metadata │ │ +│ │ │ │ +│ │ Content area with sufficient padding │ │ +│ │ for comfortable reading and scanning. │ │ +│ │ │ │ +│ ╰───────────────────────────────────────────╯ │ +│ │ +│ Properties: │ +│ • bg: var(--glass-light) │ +│ • border: 1px solid var(--border-subtle) │ +│ • border-radius: var(--radius-lg) │ +│ • backdrop-filter: blur(12px) │ +│ • padding: var(--padding-card) │ +│ │ +│ ELEVATED CARD (Interactive) │ +│ ╭───────────────────────────────────────────╮ │ +│ │ │ ← Hover: │ +│ │ Select Provider │ Lift + │ +│ │ Choose your AI model │ Glow │ +│ │ │ │ +│ │ → Claude 4 → GPT-4 │ │ +│ │ │ │ +│ ╰───────────────────────────────────────────╯ │ +│ │ +│ Hover state: │ +│ • transform: translateY(-2px) │ +│ • border-color: var(--aurora-cyan) │ +│ • box-shadow: 0 0 30px -10px var(--aurora-cyan-glow) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7.3 Input Fields + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA INPUTS │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ DEFAULT STATE │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Ask opencode anything... │ │ +│ └─────────────────────────────────────────────────┘ │ +│ bg: var(--glass-subtle) │ +│ border: var(--border-subtle) │ +│ text: var(--text-tertiary) ← placeholder │ +│ │ +│ FOCUS STATE │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ How do I implement_ ░▓▓▓ │ │ +│ ╰═════════════════════════════════════════════════╯ │ +│ ↑ ↑ │ +│ Cyan glow border Cursor pulse │ +│ │ +│ border: 2px solid var(--aurora-cyan) │ +│ box-shadow: 0 0 0 4px var(--aurora-cyan-glow) │ +│ bg: var(--glass-light) │ +│ │ +│ ERROR STATE │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ Invalid API key │ │ +│ ╰═════════════════════════════════════════════════╯ │ +│ border-color: var(--aurora-red) │ +│ box-shadow: 0 0 0 4px var(--aurora-red-glow) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7.4 The Prompt Input (Hero Component) + +This is the MOST IMPORTANT component—the main chat input: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA PROMPT INPUT │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ╭═══════════════════════════════════════════════════════╮ │ +│ ║ ║ │ +│ ║ │ How can I refactor this React component ║ │ +│ ║ │ to use hooks instead of class components?_ ║ │ +│ ║ ║ │ +│ ╟───────────────────────────────────────────────────────╢ │ +│ ║ ◎ @file ◎ @folder ◎ @web [⌘ + Enter] ║ │ +│ ╚═══════════════════════════════════════════════════════╝ │ +│ │ +│ Design Details: │ +│ • Double-line border with subtle gradient │ +│ • Inner glow when focused (aurora-cyan) │ +│ • Attachment chips below with hover states │ +│ • Send button pulses subtly when ready │ +│ • Expands smoothly as content grows │ +│ │ +│ Animation: │ +│ • On focus: border brightens, inner glow appears │ +│ • On type: subtle scale micro-pulse (1.002x) │ +│ • On send: content slides up + fades, input shrinks │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7.5 Message Bubbles + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA MESSAGE BUBBLES │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ USER MESSAGE │ +│ ╭─────────────────────────────╮ │ +│ │ How do I fix this TypeScript│ │ +│ │ error in my component? │ │ +│ ╰─────────────────────────────╯ │ +│ │ +│ • Aligned right │ +│ • bg: var(--aurora-cyan) at 15% opacity │ +│ • border-left: 2px solid var(--aurora-cyan) │ +│ • Subtle cyan tint │ +│ │ +│ ASSISTANT MESSAGE │ +│ ╭──────────────────────────────────────────────────────╮ │ +│ │ ◈ Let me help you with that TypeScript error. │ │ +│ │ │ │ +│ │ The issue is that your component expects a │ │ +│ │ `string` but receives `string | undefined`. │ │ +│ │ │ │ +│ │ ```typescript │ │ +│ │ // Add type guard │ │ +│ │ if (typeof value === 'string') { │ │ +│ │ processValue(value) │ │ +│ │ } │ │ +│ │ ``` │ │ +│ ╰──────────────────────────────────────────────────────╯ │ +│ │ +│ • Aligned left │ +│ • bg: var(--glass-light) │ +│ • border-left: 2px solid var(--aurora-violet) │ +│ • Code blocks: var(--void-elevated) bg │ +│ │ +│ STREAMING STATE │ +│ ╭──────────────────────────────────────────────────────╮ │ +│ │ ◈ Analyzing your codebase... │ │ +│ │ │ │ +│ │ ▓▓▓▓▓▓▓▓░░░░░░░░░░░░ Scanning files │ │ +│ │ ◌ ◌ ◌ ← Pulsing dots │ │ +│ ╰──────────────────────────────────────────────────────╯ │ +│ │ +│ • Shimmer effect on loading areas │ +│ • Typing indicator: 3 dots with staggered pulse │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7.6 Navigation & Header + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA HEADER │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ +│ ┃ ◈ opencode Session: Project Analysis ┃ │ +│ ┃ ⚙ ◐ ▤ ┃ │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +│ │ +│ Properties: │ +│ • bg: var(--void-elevated) with backdrop-blur │ +│ • border-bottom: 1px solid var(--border-subtle) │ +│ • position: sticky │ +│ • Logo: ◈ glyph with subtle cyan glow │ +│ • Session title: truncated with ellipsis │ +│ • Actions: icon buttons with hover glow │ +│ │ +│ SIDEBAR (Collapsed) │ +│ ┌──┐ │ +│ │◈│ ← Logo only │ +│ │──│ │ +│ │⊕│ ← New session │ +│ │📄│ ← Recent │ +│ │⚙│ ← Settings │ +│ └──┘ │ +│ │ +│ SIDEBAR (Expanded) │ +│ ╭────────────────────────╮ │ +│ │ ◈ opencode │ │ +│ ├────────────────────────┤ │ +│ │ ⊕ New Session │ │ +│ ├────────────────────────┤ │ +│ │ RECENT │ │ +│ │ ├─ Project Analysis │ ← Selected, cyan highlight │ +│ │ ├─ Code Review │ │ +│ │ └─ Bug Investigation │ │ +│ ├────────────────────────┤ │ +│ │ ⚙ Settings │ │ +│ ╰────────────────────────╯ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7.7 Dialogs & Modals + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA MODALS │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ BACKDROP │ +│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ +│ bg: rgba(5, 5, 8, 0.8) │ +│ backdrop-filter: blur(4px) │ +│ │ +│ MODAL CARD │ +│ ╭═══════════════════════════════════════════════════════╮ │ +│ ║ SELECT MODEL ✕ ║ │ +│ ╟───────────────────────────────────────────────────────╢ │ +│ ║ ║ │ +│ ║ ◉ Claude 4 Opus ║ │ +│ ║ Best for complex reasoning ║ │ +│ ║ ║ │ +│ ║ ○ Claude 4 Sonnet ║ │ +│ ║ Balanced performance ║ │ +│ ║ ║ │ +│ ║ ○ GPT-4o ║ │ +│ ║ OpenAI's flagship ║ │ +│ ║ ║ │ +│ ╟───────────────────────────────────────────────────────╢ │ +│ ║ [Cancel] [ Confirm ] ║ │ +│ ╚═══════════════════════════════════════════════════════╝ │ +│ │ +│ Entry animation: │ +│ • Backdrop fades in (0→1, 200ms) │ +│ • Modal scales + fades (0.95→1, 0→1, 250ms, spring) │ +│ │ +│ Exit animation: │ +│ • Modal scales down + fades (1→0.95, 1→0, 150ms) │ +│ • Backdrop fades out (1→0, 150ms) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Part 8: TUI (Terminal) Component Translations + +The terminal can't do true 3D or blur, but we can create the *feeling* of Aurora through: + +### 8.1 Aurora TUI Character Palette + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA TUI — CHARACTER DESIGN LANGUAGE │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ─── BORDERS ─── │ +│ Light: ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ │ +│ Rounded: ╭ ╮ ╰ ╯ │ +│ Heavy: ━ ┃ ┏ ┓ ┗ ┛ ┣ ┫ ┳ ┻ ╋ │ +│ Double: ═ ║ ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬ │ +│ │ +│ ─── AURORA PREFERENCE ─── │ +│ Primary borders: ╭ ─ ╮ (rounded, elegant) │ +│ │ │ │ +│ ╰ ─ ╯ │ +│ │ +│ Active/Focus: ╭═══╮ (double top = "glow") │ +│ │ │ │ +│ ╰───╯ │ +│ │ +│ ─── BULLETS & MARKERS ─── │ +│ Filled: ● ◉ ◆ ◈ ■ ▲ ▶ │ +│ Empty: ○ ◇ □ △ ▷ │ +│ Special: ◐ ◑ ◒ ◓ (half-filled, for progress) │ +│ │ +│ Aurora preference: │ +│ • Logo/brand: ◈ (diamond with dot = light source) │ +│ • Selected: ● │ +│ • Unselected: ○ │ +│ • Active: ◉ (ring = glow) │ +│ • Tree nodes: ├─ └─ │ │ +│ │ +│ ─── PROGRESS INDICATORS ─── │ +│ Block gradient: ░ ▒ ▓ █ │ +│ Thin bar: ─ ━ │ +│ │ +│ Aurora progress: ░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ +│ (dim → bright = filling with light) │ +│ │ +│ ─── SPINNERS ─── │ +│ Dots: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ │ +│ Circle: ◴ ◷ ◶ ◵ │ +│ Quarter: ◜ ◝ ◞ ◟ │ +│ │ +│ Aurora spinner: ◌ ◍ ◎ ● ◎ ◍ (breathing pulse) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 8.2 TUI Layout Templates + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA TUI — MAIN SESSION VIEW │ +├─────────────────────────────────────────────────────────────┤ + +╭─── ◈ opencode ─────────────────── Session: Code Review ────╮ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┃ How can I optimize this database query? │ ← User (cyan │) +│ │ +│ ╭─────────────────────────────────────────────────────╮ │ +│ │ ◈ I'll analyze your query and suggest optimizations.│ │ ← Assistant +│ │ │ │ +│ │ Looking at your query, I see several opportunities: │ │ +│ │ │ │ +│ │ 1. Add an index on `user_id` │ │ +│ │ 2. Use EXPLAIN ANALYZE to identify bottlenecks │ │ +│ │ 3. Consider pagination for large result sets │ │ +│ │ │ │ +│ │ ```sql │ │ +│ │ CREATE INDEX idx_user_id ON orders(user_id); │ │ +│ │ ``` │ │ +│ ╰─────────────────────────────────────────────────────╯ │ +│ │ +│ ░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓ Processing file changes │ ← Progress +│ │ +╰─────────────────────────────────────────────────────────────╯ +│ [?] Help [m] Model [t] Theme [s] Sessions $0.003 │ ← Footer +└─────────────────────────────────────────────────────────────┘ + +COLOR MAPPING: +• Header border: aurora-cyan (#00D4FF) +• Header text: text-primary (#F5F5F7) +• User message │: aurora-cyan +• Assistant border: aurora-violet (#A78BFA) +• Code blocks: void-elevated bg +• Progress filled: aurora-cyan +• Progress empty: border-subtle +• Footer: text-muted +``` + +### 8.3 TUI Dialog Example + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AURORA TUI — MODEL SELECTOR DIALOG │ +├─────────────────────────────────────────────────────────────┤ + + ╭═══════════════════════════════════════════╮ + ║ SELECT MODEL ║ + ╠═══════════════════════════════════════════╣ + ║ ║ + ║ ◉ Claude 4 Opus ║ ← Selected (cyan) + ║ Best for complex reasoning ║ + ║ ║ + ║ ○ Claude 4 Sonnet ║ + ║ Balanced performance ║ + ║ ║ + ║ ○ GPT-4o ║ + ║ OpenAI's flagship ║ + ║ ║ + ║ ○ Gemini 1.5 Pro ║ + ║ Google's multimodal ║ + ║ ║ + ╟───────────────────────────────────────────╢ + ║ [↑↓] Navigate [Enter] Select [Esc] ║ + ╚═══════════════════════════════════════════╝ + +COLOR MAPPING: +• Dialog border: aurora-cyan (double line = "glowing") +• Selected item: aurora-cyan fg + ◉ marker +• Unselected: text-muted + ○ marker +• Description: text-tertiary +• Keybinds: text-muted +• Dialog bg: void-elevated +``` + +--- + +## Part 9: Stitch Prompts for Visual Prototyping + +Copy & paste these prompts directly into [stitch.google.com](https://stitch.google.com) to generate visual mockups. + +### Prompt 1: Main Chat Interface (Dark Theme) + +``` +Create a dark mode AI chat interface for a developer tool called "opencode". + +DESIGN DIRECTION: +- Style: Luxury minimal, future-forward like Tesla/Rivian interiors +- Aesthetic: Digital luminescence - elements emit light rather than cast shadows +- Feel: Clean but bold, pushing boundaries while staying usable + +COLORS: +- Background: Deep void black (#0A0A0F) +- Cards/panels: Glassmorphism with very subtle white tint (rgba(255,255,255,0.04)) +- Primary accent: Electric cyan (#00D4FF) - used for glows and highlights +- Secondary accent: Soft violet (#A78BFA) +- Text: Bright white (#F5F5F7) +- Borders: Subtle glow, not hard edges + +LAYOUT: +- Left sidebar (collapsed): narrow strip with logo ◈, new session button, recent sessions list +- Main area: chat message history with clear visual hierarchy +- Bottom: prominent input field with glassmorphism, glowing cyan border on focus +- Header: session title, context/token count, settings icons + +MESSAGE STYLING: +- User messages: aligned right, subtle cyan tint background, thin cyan left border +- Assistant messages: aligned left, glass card with rounded corners, thin violet left border +- Code blocks inside messages: darker elevated background + +EFFECTS: +- Buttons glow brighter on hover (cyan halo expands) +- Cards have subtle lift on hover +- Input field has pulsing glow when focused +- Use smooth spring-based animations, not linear + +TYPOGRAPHY: +- Font: JetBrains Mono for code, Inter/Geist for UI text +- Clean, modern, monospace aesthetic + +Show this as a full desktop application interface (1440x900) with an ongoing conversation about code refactoring. +``` + +--- + +### Prompt 2: Model Selection Modal + +``` +Create a modal dialog for selecting AI models in a dark mode developer tool. + +DESIGN: +- Style: Glassmorphism modal floating over blurred dark background +- Background behind modal: Deep black (#0A0A0F) with 80% opacity overlay + blur +- Modal card: Glass effect (rgba(255,255,255,0.06)) with luminous cyan border + +MODAL CONTENT: +- Title: "SELECT MODEL" at top +- List of 4-5 AI models as selectable options: + • Claude 4 Opus - "Best for complex reasoning" + • Claude 4 Sonnet - "Balanced performance" + • GPT-4o - "OpenAI's flagship" + • Gemini 1.5 Pro - "Multimodal capabilities" + +INTERACTIONS: +- Selected option: Has filled cyan radio button, text is brighter +- Unselected: Empty circle, muted text +- Hover state: Subtle glow behind the option row +- Footer: "Cancel" ghost button, "Confirm" primary button with cyan glow + +ANIMATION: +- Modal scales in from 0.95 to 1.0 with fade +- Backdrop blurs in smoothly +- Radio selection has smooth transition + +Colors: +- Accent cyan: #00D4FF +- Background void: #0A0A0F +- Glass: rgba(255,255,255,0.06) +- Text: #F5F5F7 (primary), #A1A1AA (muted) +``` + +--- + +### Prompt 3: Empty State / Welcome Screen + +``` +Create a welcome screen for an AI coding assistant called "opencode". + +DESIGN DIRECTION: +- Dark mode, luxury minimal aesthetic +- Ethereal, digital luminescence feel +- Background: Very dark (#050508) with subtle animated gradient aurora effect (cyan/violet/rose, VERY subtle and slow) + +CONTENT: +- Large diamond logo (◈) in center, glowing softly with cyan light +- Tagline: "Code illuminated" +- Subtitle: "Your AI pair programming assistant" +- 3-4 quick action cards below: + • "Start new session" (primary CTA with cyan glow) + • "Continue recent: [session name]" + • "Explore templates" + • "Settings & preferences" + +VISUAL EFFECTS: +- Logo has subtle breathing pulse (glow expands/contracts slowly) +- Quick action cards are glass panels that lift and glow on hover +- Very subtle particle/star field effect in background (optional, keep it minimal) +- Typography is clean, modern, confident + +COLORS: +- Primary: #00D4FF (cyan) +- Secondary: #A78BFA (violet) +- Tertiary: #FF6B9D (rose) +- Background: #050508 to #0A0A0F gradient +- Text: #F5F5F7 + +Show as full screen application (1440x900), centered composition. +``` + +--- + +### Prompt 4: Light Theme Variant + +``` +Create a light mode variant of an AI chat interface for developers. + +DESIGN DIRECTION: +- Same luxury minimal aesthetic as dark mode, but inverted +- "Daylight aurora" - colors are richer/deeper for contrast +- Feel: Clean, bright, professional, premium + +COLORS: +- Background: Soft pearl (#FAFAFA) +- Cards: Frosted white glass with very subtle shadows +- Primary accent: Deeper cyan (#0891B2) for contrast +- Secondary: Rich violet (#7C3AED) +- Text: Near-black (#18181B) +- Borders: Very subtle gray, barely visible + +LAYOUT (same as dark): +- Collapsed sidebar on left +- Chat messages in center +- Glowing input at bottom (cyan glow still works in light mode) + +MESSAGE STYLING: +- User: Subtle cyan wash background, deeper cyan left border +- Assistant: White glass card, violet left border +- Code blocks: Light gray (#F4F4F5) background + +KEY DIFFERENCE FROM DARK: +- Shadows can be used (subtle, soft) +- Glass effect uses slight darkness instead of lightness +- Accents are richer/more saturated +- Same spring animations, same glow effects on focus + +Show as desktop app (1440x900) with same conversation as dark version. +``` + +--- + +### Prompt 5: Session List / Sidebar Expanded + +``` +Create an expanded sidebar view for a developer chat application. + +DESIGN: +- Dark mode, glassmorphism sidebar panel +- Sidebar width: ~280px +- Background: Slightly elevated from main (#0F0F14) + +CONTENT: +- Top: Logo "◈ opencode" with subtle cyan glow +- Below logo: "+ New Session" button (primary, cyan glow) +- Section: "RECENT" label (small, muted, uppercase) +- Session list items showing: + • Session title (truncated) + • Brief preview of last message + • Timestamp (relative: "2h ago", "Yesterday") + • Subtle icon showing model used + +INTERACTIONS: +- Current/selected session: Cyan highlight bar on left, brighter text +- Hover: Glass background appears, subtle glow +- List items have smooth slide-in animation on load + +VISUAL DETAILS: +- Divider lines are very subtle (border-subtle) +- Sessions grouped by time (Today, Yesterday, This Week) +- Scroll area with fading edge at top/bottom +- Search input at top with glass styling + +Colors: +- Selected highlight: #00D4FF +- Muted text: #71717A +- Timestamps: #A1A1AA +``` + +--- + +### Prompt 6: Tool/Permission Dialog + +``` +Create a permission request dialog for an AI coding assistant. + +CONTEXT: +The AI wants to edit a file and needs user approval. + +DESIGN: +- Dark glassmorphism modal +- Slightly different accent - using amber/warning color to indicate caution + +CONTENT: +- Header icon: Edit/pencil icon with amber glow +- Title: "Edit File Request" +- Description: "opencode wants to modify:" +- File path displayed: `src/components/Button.tsx` +- Preview section showing diff: + • Green highlighted lines for additions + • Red highlighted lines for deletions + • Context lines in muted color + +ACTIONS: +- "Allow" button - Primary with amber accent (#FFBB33) +- "Allow All" button - Secondary +- "Deny" button - Ghost/danger hint + +VISUAL DETAILS: +- Diff preview has code syntax highlighting +- Line numbers visible +- Modal has amber-tinted border (warning state) +- Keep the same glass effect and animation patterns + +Show the dialog centered over a blurred chat interface background. +``` + +--- + +### Prompt 7: Loading/Processing State + +``` +Create a message streaming/loading state for an AI response. + +DESIGN: +- Dark mode chat interface +- Assistant message in progress of being generated + +VISUAL: +- Glass card for assistant message (violet left border) +- Inside the card: + • "◈" logo pulsing with violet glow (breathing animation) + • First line of text appearing with typewriter effect + • Remaining area has subtle shimmer/skeleton loader + • Three dots "◌ ◌ ◌" with staggered pulse animation + +PROGRESS INDICATOR: +- Horizontal progress bar at bottom of card +- Uses the "filling with light" metaphor +- Empty portion: dim gray (░░░) +- Filled portion: cyan gradient (▓▓▓) +- Shows: "Analyzing codebase... 127 files" + +EFFECTS: +- Text fades in word by word +- Shimmer effect uses subtle gradient animation +- Overall feel: the AI is "thinking" and response is "materializing from light" + +Keep consistent with the Aurora design system - ethereal, luminous, not mechanical. +``` + +--- + +### Prompt 8: Settings Panel + +``` +Create a settings/preferences panel for a developer AI tool. + +DESIGN: +- Full-width panel that slides in from right (or modal) +- Dark glassmorphism style +- Organized into clear sections + +SECTIONS: +1. APPEARANCE + - Theme selector (dropdown or visual cards) + - Font size slider + +2. MODEL DEFAULTS + - Default model dropdown + - Temperature slider with visual indicator + +3. KEYBINDINGS + - List of keyboard shortcuts in two columns + - Each shows action + keybind + +4. INTEGRATIONS + - Toggle switches for: Git, LSP, MCP servers + - Each with subtle description + +VISUAL STYLE: +- Section headers: Small caps, cyan accent, muted +- Form controls: Glass styling, cyan focus states +- Toggle switches: Off = muted, On = cyan glow +- Sliders: Thin track, glowing thumb + +LAYOUT: +- Clean vertical stack with generous spacing +- Dividers between sections (very subtle) +- "Save" and "Cancel" buttons at bottom + +Background: #0F0F14 (elevated from main void) +``` + +--- + +### Prompt 9: Error State + +``` +Create an error notification/toast for an AI coding assistant. + +SCENARIO: API rate limit exceeded + +DESIGN: +- Toast notification appearing at top-right +- Dark glass with RED accent (error state) + +CONTENT: +- Left: Warning icon with red glow (⚠ or !) +- Title: "Rate Limit Exceeded" +- Description: "Please wait 30 seconds before trying again" +- Dismiss X button on right + +VISUAL: +- Glass background with subtle red tint +- Red left border (2-3px) +- Soft red outer glow (not harsh) +- Red accent: #F87171 + +ANIMATION: +- Slides in from right with spring physics +- Slight bounce at end +- Auto-dismiss with progress bar along bottom +- Fades out when dismissed + +ERROR COLOR MAPPING: +- Error: #F87171 (rose-red) +- Warning: #FFBB33 (amber) +- Success: #4ADE80 (green) +- Info: #00D4FF (cyan) + +Show toast over blurred chat interface. +``` + +--- + +### Prompt 10: Code Diff View + +``` +Create a code diff viewer for an AI coding assistant's file changes. + +DESIGN: +- Dark mode with syntax highlighting +- Side-by-side or unified diff view + +VISUAL STRUCTURE: +- Header: File path, "View Full File" link +- Line numbers on left (muted color) +- Two-tone background for changes: + • Added lines: Very subtle green tint background (#0D2818) + • Removed lines: Very subtle red tint background (#2D1216) + • Context lines: Default void background + +SYNTAX HIGHLIGHTING (Aurora theme): +- Keywords: Violet (#A78BFA) +- Functions: Cyan (#00D4FF) +- Strings: Green (#4ADE80) +- Numbers: Rose (#FF6B9D) +- Comments: Muted gray (#71717A) +- Variables: White (#F5F5F7) + +ADDITIONS: +- + symbol in green +- Line highlighted with green left border +- Changed text within line has brighter green background + +DELETIONS: +- - symbol in red +- Line highlighted with red left border +- Changed text within line has brighter red background + +GLASS CARD: +- Wrap entire diff in glass panel +- Rounded corners +- Subtle border + +Show a realistic diff of a TypeScript React component being refactored. +``` + +--- + +## Part 10: Final Summary & Implementation Guide + +### Design DNA at a Glance + +``` +┌─────────────────────────────────────────────────────────────┐ +│ │ +│ AURORA DESIGN SYSTEM │ +│ "Code illuminated from within" │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ VISUAL MOTION │ +│ ├─ Dark-first luxury ├─ Spring physics │ +│ ├─ Digital luminescence ├─ Confident/tactile │ +│ ├─ Glassmorphism ├─ 200-350ms timing │ +│ └─ Glowing accents └─ Glow as feedback │ +│ │ +│ COLOR TYPOGRAPHY │ +│ ├─ Cyan primary ├─ JetBrains Mono (code) │ +│ ├─ Violet secondary ├─ Geist/Inter (UI) │ +│ ├─ Rose tertiary └─ Major Third scale │ +│ └─ Void backgrounds │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Quick Reference Card + +| Aspect | Specification | +|--------|---------------| +| **Primary Accent** | `#00D4FF` (Electric Cyan) | +| **Secondary** | `#A78BFA` (Soft Violet) | +| **Tertiary** | `#FF6B9D` (Rose) | +| **Dark Background** | `#0A0A0F` (Void) | +| **Light Background** | `#FAFAFA` (Pearl) | +| **Border Style** | Subtle glow, not hard edges | +| **Glass Effect** | `rgba(255,255,255,0.04)` + `blur(12px)` | +| **Border Radius** | `8px` buttons, `12px` cards, `24px` modals | +| **Animation Duration** | 150-350ms | +| **Easing** | Spring-based (`cubic-bezier(0.22, 1, 0.36, 1)`) | +| **Code Font** | JetBrains Mono | +| **UI Font** | Geist / Inter | + +### Component Mapping: Web → TUI + +| Web Component | TUI Equivalent | +|---------------|----------------| +| Cyan glow border | Double-line border `═══` | +| Glassmorphism card | Rounded box `╭─╮ │ ╰─╯` | +| Hover lift effect | Highlight color change | +| Loading shimmer | Block gradient `░▒▓█` | +| Pulsing glow | Braille spinner `⠋⠙⠹...` or `◌◍◎●` | +| User cyan tint | Cyan foreground + `┃` pipe | +| Assistant violet border | Violet `│` left margin | + +### Implementation Phases (Recommended) + +#### Phase 1: Theme Foundation +- [ ] Create `aurora-dark.json` and `aurora-light.json` theme files +- [ ] Add to TUI theme selector +- [ ] Update CSS custom properties for web console + +#### Phase 2: Core Components +- [ ] Buttons (primary, secondary, ghost, danger) +- [ ] Input fields with focus glow +- [ ] Cards with glass effect +- [ ] Modals with backdrop blur + +#### Phase 3: Chat Interface +- [ ] Message bubbles (user/assistant) +- [ ] Prompt input (hero component) +- [ ] Loading/streaming states +- [ ] Code blocks with Aurora syntax theme + +#### Phase 4: Motion Polish +- [ ] Spring animations library integration +- [ ] Enter/exit transitions +- [ ] Micro-interactions +- [ ] Loading states + +### Existing Component Touchpoints + +Based on analysis of the codebase, these are the key files to modify: + +**TUI Theme System:** +- `packages/opencode/src/cli/cmd/tui/context/theme.tsx` — Theme provider and color types +- `packages/opencode/src/cli/cmd/tui/context/theme/` — Theme JSON files (add aurora-dark.json, aurora-light.json) + +**TUI Components:** +- `packages/opencode/src/cli/cmd/tui/routes/session/index.tsx` — Main session view +- `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx` — Prompt input component +- `packages/opencode/src/cli/cmd/tui/component/dialog-*.tsx` — All dialog components + +**Web Console:** +- `packages/console/app/src/style/token/color.css` — CSS color tokens +- `packages/console/app/src/routes/index.css` — Landing page styles +- `packages/console/app/src/component/` — Shared components + +### Success Criteria + +The Aurora redesign is successful when: + +1. **Visual Coherence**: TUI and Web feel like the same product family +2. **Motion Quality**: Interactions feel tactile and confident, not floaty or delayed +3. **Performance**: Animations run at 60fps, no jank +4. **Accessibility**: 4.5:1 contrast ratios maintained, focus states visible +5. **Brand Recognition**: Users recognize "the opencode look" instantly + +--- + +## Appendix: Theme JSON Template + +```json +{ + "$schema": "https://opencode.ai/theme.json", + "defs": { + "voidDeepest": "#050508", + "voidDeep": "#0A0A0F", + "voidBase": "#0F0F14", + "voidElevated": "#14141A", + "voidHover": "#1A1A22", + "auroraCyan": "#00D4FF", + "auroraViolet": "#A78BFA", + "auroraRose": "#FF6B9D", + "auroraAmber": "#FFBB33", + "auroraGreen": "#4ADE80", + "auroraRed": "#F87171", + "textPrimary": "#F5F5F7", + "textSecondary": "#A1A1AA", + "textTertiary": "#71717A" + }, + "theme": { + "primary": "auroraCyan", + "secondary": "auroraViolet", + "accent": "auroraRose", + "error": "auroraRed", + "warning": "auroraAmber", + "success": "auroraGreen", + "info": "auroraCyan", + "text": "textPrimary", + "textMuted": "textSecondary", + "background": "voidDeep", + "backgroundPanel": "voidBase", + "backgroundElement": "voidElevated", + "border": "#1E1E26", + "borderActive": "auroraCyan", + "borderSubtle": "#14141A", + "syntaxKeyword": "auroraViolet", + "syntaxFunction": "auroraCyan", + "syntaxString": "auroraGreen", + "syntaxNumber": "auroraRose", + "syntaxComment": "textTertiary", + "syntaxVariable": "textPrimary", + "syntaxType": "auroraAmber", + "syntaxOperator": "textSecondary", + "syntaxPunctuation": "textTertiary", + "diffAdded": "auroraGreen", + "diffRemoved": "auroraRed", + "diffAddedBg": "#0D2818", + "diffRemovedBg": "#2D1216", + "diffContext": "textTertiary", + "diffContextBg": "voidBase" + } +} +``` + +--- + +**Document created:** 2025-02-26 +**Design direction:** Aurora — Digital Luminescence +**Status:** Ready for implementation +**Last reviewed:** 2025-02-26 (UI/UX Pro Max review incorporated) + +--- + +## Part 11: Accessibility & Review Amendments + +*This section addresses feedback from the UI/UX Pro Max review and adds critical accessibility requirements.* + +### 11.1 Motion Sickness Prevention (CRITICAL) + +**Issue:** The original design suggested animated aurora backgrounds and continuous pulse effects which can trigger motion sensitivity. + +**Resolution:** + +```css +/* ═══════════════════════════════════════════════════════════ + REDUCED MOTION SUPPORT — MANDATORY + ═══════════════════════════════════════════════════════════ */ + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + /* Disable specific Aurora effects */ + .aurora-background { + background: var(--void-deep) !important; + animation: none !important; + } + + .glow-pulse { + box-shadow: none !important; + } +} +``` + +**Guidelines:** +- ❌ **NEVER** use infinite animations on backgrounds or decorative elements +- ✅ Continuous animation ONLY permitted during active loading states +- ✅ Aurora drift effect should be opt-in, disabled by default +- ✅ All spring animations must have `prefers-reduced-motion` fallback + +--- + +### 11.2 Line Length Constraints (HIGH) + +**Issue:** Chat interfaces and documentation need line-length limits for readability. + +**Resolution:** + +```css +/* Add to spacing system */ +:root { + --max-prose-width: 70ch; /* 65-75 characters optimal */ +} + +/* Apply to text containers */ +.chat-message, +.documentation-content, +.modal-body { + max-width: var(--max-prose-width); +} + +/* Ensure full-width code blocks still work */ +.code-block { + max-width: 100%; + overflow-x: auto; +} +``` + +**Application:** +| Component | Max Width | +|-----------|-----------| +| Chat message bubbles | `70ch` | +| Modal body text | `70ch` | +| Documentation paragraphs | `70ch` | +| Code blocks | `100%` (scrollable) | +| Headers | No limit | + +--- + +### 11.3 Light Mode Glass Contrast (CRITICAL) + +**Issue:** Light mode glass effects were too subtle to establish visual hierarchy. + +**Resolution — Updated Light Theme:** + +```css +:root[data-theme="aurora-light"] { + /* ─── ADJUSTED GLASS OPACITIES ─── */ + --glass-subtle: rgba(0, 0, 0, 0.03); /* was 0.02 */ + --glass-light: rgba(0, 0, 0, 0.06); /* was 0.04 */ + --glass-medium: rgba(0, 0, 0, 0.09); /* was 0.06 */ + --glass-strong: rgba(0, 0, 0, 0.12); /* was 0.08 */ + + /* ─── STRONGER BORDERS ─── */ + --border-subtle: rgba(0, 0, 0, 0.08); /* was 0.06 */ + --border-default: rgba(0, 0, 0, 0.12); /* was 0.10 */ + --border-strong: rgba(0, 0, 0, 0.18); /* was 0.15 */ + + /* ─── SUBTLE SHADOWS (light mode only) ─── */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.10); +} + +/* Apply shadows to cards in light mode only */ +[data-theme="aurora-light"] .glass-card { + box-shadow: var(--shadow-sm); +} +``` + +**Contrast Verification:** + +| Text | Background | Ratio | Status | +|------|------------|-------|--------| +| `#18181B` | `#FAFAFA` | 16.2:1 | ✅ Pass | +| `#52525B` | `#FAFAFA` | 7.4:1 | ✅ Pass | +| `#A1A1AA` | `#FAFAFA` | 3.0:1 | ⚠️ Large text only | +| `#F5F5F7` | `#0A0A0F` | 19.6:1 | ✅ Pass | +| `#A1A1AA` | `#0A0A0F` | 8.5:1 | ✅ Pass | + +--- + +### 11.4 Interactive Element Requirements (MEDIUM) + +**Issue:** Interactive cues need explicit mandates. + +**Resolution — Mandatory Interaction Patterns:** + +```css +/* All clickable elements */ +button, +[role="button"], +.clickable, +.interactive-card, +a { + cursor: pointer; +} + +/* Focus-visible states (keyboard navigation) */ +:focus-visible { + outline: 2px solid var(--aurora-cyan); + outline-offset: 2px; +} + +/* Disable outline for mouse users */ +:focus:not(:focus-visible) { + outline: none; +} +``` + +**Icon Standards:** +- ✅ **Required:** Lucide Icons (React: `lucide-react`, Web: `lucide`) +- ✅ **Acceptable:** Heroicons, Phosphor Icons +- ❌ **Forbidden:** Emoji as UI icons (OS rendering inconsistency) +- ❌ **Forbidden:** Font Awesome (too generic, doesn't fit Aurora aesthetic) + +--- + +### 11.5 WCAG Compliance Checklist + +Before implementation, verify: + +#### Color & Contrast +- [ ] All body text has 4.5:1 minimum contrast ratio +- [ ] All large text (18px+) has 3:1 minimum contrast ratio +- [ ] Focus indicators are clearly visible (2px cyan outline) +- [ ] Error states use red AND icon/text (not color alone) + +#### Motion & Animation +- [ ] `prefers-reduced-motion` media query implemented +- [ ] No infinite animations on decorative elements +- [ ] Loading animations can be paused or are under 5s +- [ ] No flashing content (3 flashes per second limit) + +#### Interaction +- [ ] All interactive elements have `cursor: pointer` +- [ ] Touch targets are minimum 44x44px +- [ ] Keyboard navigation follows visual order +- [ ] Focus states are distinct from hover states + +#### Typography +- [ ] Minimum 16px body text (mobile) +- [ ] Line height minimum 1.5 for body text +- [ ] Line length limited to 70ch for prose +- [ ] Text is resizable to 200% without loss of functionality + +--- + +### Review Response Summary + +| Feedback Item | Severity | Action Taken | +|---------------|----------|--------------| +| Motion sickness / `prefers-reduced-motion` | CRITICAL | Added §11.1 with full CSS implementation | +| Line length 65-75ch | HIGH | Added §11.2 with `--max-prose-width: 70ch` | +| Light mode glass contrast | CRITICAL | Added §11.3 with adjusted opacity values | +| `cursor-pointer` mandate | MEDIUM | Added §11.4 with interactive patterns | +| SVG icons only | MEDIUM | Added §11.4 with Lucide Icons mandate | +| WCAG compliance | — | Added §11.5 checklist | + +--- + +*Review incorporated from: UI/UX Pro Max analysis (2025-02-26)* diff --git a/docs/plans/2025-02-26-roo-code-orphaned-tool-use-debug.md b/docs/plans/2025-02-26-roo-code-orphaned-tool-use-debug.md new file mode 100644 index 000000000000..4c70e250500a --- /dev/null +++ b/docs/plans/2025-02-26-roo-code-orphaned-tool-use-debug.md @@ -0,0 +1,442 @@ +# Systematic Root-Cause Analysis: Roo Code Orphaned `tool_use` Error + +## Phase 1: Root Cause Investigation + +### 1.1 The Error (Read Carefully) + +``` +messages.12: `tool_use` ids were found without `tool_result` blocks immediately after: + tooluse_gcKGmk7V7opjkl8G2V6v0N, tooluse_ldg9S86J2GK8UzcQqvOQXR. +Each `tool_use` block must have a corresponding `tool_result` block in the next message. +``` + +**What this tells us precisely:** + +| Fact | Implication | +| ---------------------------------------------------- | --------------------------------------------------------------------------------- | +| `messages.12` | The **13th message** (0-indexed) in the conversation array is the problem | +| Two IDs: `tooluse_gcKG…`, `tooluse_ldg9…` | The assistant called **exactly 2 tools** in that turn | +| "without `tool_result` blocks **immediately after**" | Message 13 (the next user message) does NOT contain matching `tool_result` blocks | +| Cost `$0.0000` | API rejects the request **before** processing — this is a pre-validation error | +| First attempt at 7%, $1.61 spent | **~6 successful API round-trips** happened before this (at ~$0.25/turn) | +| IDs start with `tooluse_` | This is Anthropic's native tool calling format (not OpenAI-style `call_*`) | + +### 1.2 Reproducing the Scenario + +**User's task:** "Create a latest DMG for me, before that redesign the SVG for the app icon. Use Skill UI UX Pro Max." + +This task would trigger the following tool sequence: + +1. **read_file** — Read existing SVG icon file(s) +2. **list_files** — Scan project structure for icon locations +3. **write_to_file** — Write new SVG design +4. **execute_command** — Build/package DMG + +The **"Use Skill UI UX Pro Max"** instruction is key — it tells the assistant to use a custom mode/skill, which could trigger a **`switch_mode`** or **`skill`** tool call alongside regular tools. + +At the point of failure (message 12, ~7% progress, $1.61), the assistant would have been in the early **file reading/scanning phase**, likely calling 2 tools in parallel. + +### 1.3 Backward Trace: From Error to Root Cause + +``` +Error received by: Anthropic API server + ↑ Sent by: this.api.createMessage() at Task.ts:4271 + ↑ Built from: cleanConversationHistory at Task.ts:4193 + ↑ Derived from: effectiveHistory → mergedForApi → messagesWithoutImages + ↑ Sourced from: this.apiConversationHistory (the persistent storage) + ↑ CORRUPTED HERE: message 12 has tool_use but message 13 lacks tool_result +``` + +**The question is: HOW did message 13 get saved without the tool_results?** + +There are exactly 3 code paths that save user messages with `tool_result` blocks: + +#### Path A: Normal tool execution flow (`recursivelyMakeClineRequests`) + +``` +Task.ts:3542 → Save assistant message (with tool_use blocks) +Task.ts:3561 → presentAssistantMessage(this) → executes tools → pushToolResult() +Task.ts:3581 → pWaitFor(() => this.userMessageContentReady) +Task.ts:2651 → addToApiConversationHistory({ role: "user", content: finalUserContent }) +``` + +In this path, `finalUserContent` at line 2641 includes `this.userMessageContent` which is populated by `pushToolResult` during tool execution. The `pWaitFor` at 3581 blocks until all tools complete. + +**Could this path lose tool_results?** → Only if `presentAssistantMessage` fails to call `pushToolResult`. + +#### Path B: `flushPendingToolResultsToHistory()` (delegation via `new_task`) + +``` +Task.ts:1048 → Check userMessageContent.length > 0 +Task.ts:1067 → Wait for assistantMessageSavedToHistory (30s timeout) +Task.ts:1085 → Build user message from this.userMessageContent +Task.ts:1096 → Push to apiConversationHistory +``` + +**Could this path lose tool_results?** → Yes, if abort/timeout triggers. + +#### Path C: Task resume (`resumeTaskFromHistory`) + +``` +Task.ts:2109-2117 → Generate placeholder tool_results for all tool_use blocks +Task.ts:2142-2159 → Find missing tool_results and fill them in +Task.ts:2217 → overwriteApiConversationHistory(modifiedApiConversationHistory) +``` + +**Could this path lose tool_results?** → No, it explicitly generates them. + +--- + +### 1.4 The Root Cause: `presentAssistantMessage` + `AskIgnoredError` = Silent Failure + +Here is the critical code path that causes the corruption: + +#### Step 1: Stream completes, assistant has 2 tool_use blocks + +At [`Task.ts:3542`](references/Roo-Code/src/core/task/Task.ts:3542): + +```ts +await this.addToApiConversationHistory( + { role: "assistant", content: assistantContent }, // Contains 2 tool_use blocks + reasoningMessage || undefined, +) +this.assistantMessageSavedToHistory = true // ← message 12 is now persisted +``` + +#### Step 2: Tools begin executing via `presentAssistantMessage` + +At [`presentAssistantMessage.ts:61`](references/Roo-Code/src/core/assistant-message/presentAssistantMessage.ts:61), the function is called to process each tool_use block. Each tool handler calls `askApproval()` which internally calls `cline.ask()`. + +#### Step 3: `ask()` throws `AskIgnoredError` — the silent killer + +At [`Task.ts:1304`](references/Roo-Code/src/core/task/Task.ts:1304): + +```ts +throw new AskIgnoredError("updating existing partial") +``` + +And at [`Task.ts:1312`](references/Roo-Code/src/core/task/Task.ts:1312): + +```ts +throw new AskIgnoredError("new partial") +``` + +This error is thrown when: + +- A tool starts streaming its approval request as a partial message +- Another partial update comes in before the user responds +- The earlier ask is **silently abandoned** + +#### Step 4: `handleError` catches `AskIgnoredError` but DOES NOTHING + +At [`presentAssistantMessage.ts:540-544`](references/Roo-Code/src/core/assistant-message/presentAssistantMessage.ts:540): + +```ts +const handleError = async (action: string, error: Error) => { + // Silently ignore AskIgnoredError - this is an internal control flow + // signal, not an actual error. + if (error instanceof AskIgnoredError) { + return // ← NO tool_result pushed! Silent return! + } + // ... + pushToolResult(formatResponse.toolError(errorString)) +} +``` + +**THIS IS THE BUG.** + +When `AskIgnoredError` is caught: + +- `pushToolResult()` is **never called** +- `hasToolResult` remains `false` +- The `tool_use` block has **no corresponding `tool_result`** +- But the tool handler returns normally (no re-throw) + +#### Step 5: The loop continues, user message gets saved incomplete + +After `presentAssistantMessage` completes all blocks: + +- `userMessageContentReady` is set to `true` +- The `pWaitFor` at [`Task.ts:3581`](references/Roo-Code/src/core/task/Task.ts:3581) resolves +- The user message is saved at [`Task.ts:2651`](references/Roo-Code/src/core/task/Task.ts:2651) with **1 out of 2 tool_results** (or 0 out of 2) +- The `validateAndFixToolResultIds` at [`Task.ts:1016`](references/Roo-Code/src/core/task/Task.ts:1016) SHOULD catch this... + +#### Step 6: But wait — does `validateAndFixToolResultIds` catch it? + +At [`validateToolResultIds.ts:118-121`](references/Roo-Code/src/core/task/validateToolResultIds.ts:118): + +```ts +const missingToolUseIds = toolUseBlocks + .filter((toolUse) => !existingToolResultIds.has(toolUse.id)) + .map((toolUse) => toolUse.id) +``` + +Yes, it detects the missing IDs. And at line 220-228: + +```ts +const missingToolResults = stillMissingToolUseIds.map((toolUse) => ({ + type: "tool_result" as const, + tool_use_id: toolUse.id, + content: "Tool execution was interrupted before completion.", +})) +const finalContent = missingToolResults.length > 0 ? [...missingToolResults, ...correctedContent] : correctedContent +``` + +**It injects placeholder tool_results!** So... why does the error still happen? + +#### Step 7: THE REAL BUG — `askApproval` catches `AskIgnoredError` but the tool handler itself ALSO throws it + +Look at the tool handler flow more carefully. The `askApproval` function at [`presentAssistantMessage.ts:494-529`](references/Roo-Code/src/core/assistant-message/presentAssistantMessage.ts:494) calls `cline.ask()`. If `ask()` throws `AskIgnoredError`, it **propagates up through `askApproval`**: + +```ts +const askApproval = async (...) => { + const { response, text, images } = await cline.ask(type, ...) // ← throws AskIgnoredError! + // code below never executes +} +``` + +The `AskIgnoredError` escapes `askApproval`, enters the tool handler (e.g., `readFileTool.handle()`), which catches it through `handleError`: + +```ts +// Inside a tool handler like readFileTool: +try { + const approved = await askApproval("tool", ...) // ← AskIgnoredError thrown here + // never reaches pushToolResult() +} catch (error) { + await handleError("reading file", error) // ← silently returns for AskIgnoredError +} +``` + +After `handleError` silently returns: + +- **No `tool_result` was pushed** +- The tool handler returns normally +- `presentAssistantMessage` moves to the next block + +**But this should be caught by `validateAndFixToolResultIds`...** unless there's a timing issue. + +#### Step 8: THE ACTUAL ROOT CAUSE — The AskIgnoredError is thrown DURING tool approval streaming, which happens DURING the API response stream + +The key insight is **when** this happens: + +1. The API response is still streaming (`didCompleteReadingStream = false`) +2. `presentAssistantMessage` is called to present tool #1 (partial) +3. Tool #1 calls `askApproval(type, partialMessage, progressStatus)` with `partial=true` +4. `ask()` throws `AskIgnoredError("new partial")` for the first partial +5. `handleError` silently ignores it — **no tool_result pushed** +6. `presentAssistantMessage` unlocks at line 933 and returns +7. Stream continues, tool #1 becomes complete (non-partial) +8. `presentAssistantMessage` is called again +9. **But now `cline.currentStreamingContentIndex` has already been incremented at line 957** +10. The complete version of tool #1 is **SKIPPED** — it was "already presented" as partial +11. Tool #2 is presented and executed +12. Tool #2's `tool_result` IS pushed + +So the final user message has: `[tool_result for tool #2]` but NOT `[tool_result for tool #1]`. + +**WAIT** — let me re-read line 940 more carefully: + +```ts +if (!block.partial || cline.didRejectTool || cline.didAlreadyUseTool) { +``` + +This only advances the index when `!block.partial`. A partial block does NOT advance the index. So tool #1 partial → `AskIgnoredError` → returns WITHOUT advancing index → tool #1 complete → presented again → should work. + +Let me trace more carefully... + +#### Step 8 (Revised): The REAL root cause — `AskIgnoredError` thrown for a NON-PARTIAL tool + +The `AskIgnoredError` can be thrown even for non-partial asks. Look at [`Task.ts:1474-1476`](references/Roo-Code/src/core/task/Task.ts:1474): + +```ts +throw new AskIgnoredError("superseded") +``` + +This happens when `this.lastMessageTs !== askTs` — meaning **another ask was created while this one was pending**. This is the "superseded" case. + +**Scenario for 2 parallel tools:** + +1. Stream completes with 2 tool_use blocks: `[tool_A, tool_B]` +2. `presentAssistantMessage` processes tool_A (complete, non-partial) +3. tool_A calls `askApproval("tool", ...)` → calls `cline.ask("tool", ...)` +4. `ask()` creates a new ClineMessage with `askTs = Date.now()` +5. `ask()` reaches `pWaitFor` at line 1444, waiting for user response +6. **Auto-approval kicks in** at line 1368 → `this.approveAsk()` → sets `askResponse` +7. `pWaitFor` resolves → `ask()` returns → tool_A executes → `pushToolResult()` ✓ +8. `presentAssistantMessage` increments index to tool_B +9. tool_B calls `askApproval("tool", ...)` → calls `cline.ask("tool", ...)` +10. This works normally too. ✓ + +So parallel tools in sequence shouldn't cause the issue with auto-approval. BUT: + +#### Step 8 (Final): The TRUE root cause — Mid-stream crash between assistant save and tool execution + +Let me look at the exception handler at [`Task.ts:3722-3729`](references/Roo-Code/src/core/task/Task.ts:3722): + +```ts +} catch (error) { + // This should never happen since the only thing that can throw an + // error is the attemptApiRequest, which is wrapped in a try catch + // that sends an ask where if noButtonClicked, will clear current + // task and destroy this instance. + return true // Needs to be true so parent loop knows to end task. +} +``` + +And the `presentAssistantMessage` at line 62-64: + +```ts +if (cline.abort) { + throw new Error(`[Task#presentAssistantMessage] task ... aborted`) +} +``` + +**HERE IS THE ACTUAL ROOT CAUSE:** + +1. Assistant message with 2 `tool_use` blocks is saved to history (line 3542) ← **message 12** +2. `this.assistantMessageSavedToHistory = true` (line 3546) +3. `presentAssistantMessage(this)` is called (line 3561) to present partial blocks +4. During tool execution, **`cline.abort` gets set to `true`** (user cancels, or error, or timeout) +5. `presentAssistantMessage` throws at line 63: `throw new Error("...aborted")` +6. This throw propagates up through the tool execution +7. **`pushToolResult` was never called for either tool** +8. The error reaches the `catch` at Task.ts:3722 +9. It returns `true` — task ends +10. **BUT message 12 (assistant with 2 tool_use blocks) is ALREADY in the persistent history** +11. **No user message with tool_results was ever saved as message 13** + +When the user resumes the task: + +- `resumeTaskFromHistory` at Task.ts:2090+ checks the LAST message +- If the last message is the assistant with tool_use, it generates placeholders → **works** +- But if other messages were appended AFTER message 12 before the abort (e.g., error messages, api_req_started), the last message is NOT message 12 +- The resume logic only fixes the last assistant-user pair, not arbitrary positions + +**The corruption is permanent.** + +--- + +## Phase 2: Pattern Analysis + +### Working example + +When `abort` is NOT set during tool execution: + +1. All tools execute normally +2. All `pushToolResult()` calls complete +3. `userMessageContent` has all `tool_result` blocks +4. User message saved with all results → ✓ + +### Broken example (this bug) + +When `abort` IS set during tool execution (e.g., user clicks cancel, network timeout, extension deactivation): + +1. Some tools may have executed, others not +2. `presentAssistantMessage` throws on abort check +3. `userMessageContent` has partial or zero `tool_result` blocks +4. User message is NEVER saved (abort exits the loop) +5. But assistant message with `tool_use` blocks is ALREADY saved → ✗ + +### The key difference + +The **assistant message is saved BEFORE tool execution** (line 3542), but the **user message with tool_results is saved AFTER all tools complete** (line 2651). Any interruption between these two writes creates an orphaned `tool_use`. + +--- + +## Phase 3: Hypothesis + +**Hypothesis:** The root cause is that aborting/cancelling a task between the assistant message save (Task.ts:3542) and the user message save (Task.ts:2651) leaves the API conversation history in an invalid state where an assistant message has `tool_use` blocks without a following `tool_result` message. The `validateAndFixToolResultIds` safety net only runs at write-time for new messages, not as a pre-flight check before API calls, so the corruption is never repaired on retry. + +**Evidence supporting this:** + +1. Error occurs at a fixed position (`messages.12`) — consistent with a single write of assistant message followed by no user message write +2. Two tool_use IDs — consistent with a multi-tool call that was interrupted +3. Task was at 7% progress — early in execution, tools were still being called +4. The error is **permanent** — every retry hits the same corrupted history because no code path repairs it +5. Roo Code explicitly has comments about this risk in the codebase (lines 3401-3404, 1054-1057) + +--- + +## Phase 4: Proposed Fix + +### Fix 1: Pre-flight history validation in `attemptApiRequest` + +At [`Task.ts:4193`](references/Roo-Code/src/core/task/Task.ts:4193), after building `cleanConversationHistory`, add: + +```ts +// Repair orphaned tool_use blocks before sending to API +for (let i = 0; i < cleanConversationHistory.length - 1; i++) { + const msg = cleanConversationHistory[i] + const next = cleanConversationHistory[i + 1] + + if (msg.role !== "assistant") continue + + const content = Array.isArray(msg.content) ? msg.content : [] + const toolUseBlocks = content.filter((b) => b.type === "tool_use") + if (toolUseBlocks.length === 0) continue + + if (next.role !== "user") { + // Insert a synthetic user message with tool_results + const toolResults = toolUseBlocks.map((t) => ({ + type: "tool_result", + tool_use_id: t.id, + content: "Tool execution was interrupted.", + })) + cleanConversationHistory.splice(i + 1, 0, { role: "user", content: toolResults }) + continue + } + + // Check if next user message has all required tool_results + const nextContent = Array.isArray(next.content) ? next.content : [] + const resultIds = new Set(nextContent.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id)) + const missing = toolUseBlocks.filter((t) => !resultIds.has(t.id)) + + if (missing.length > 0) { + const repairs = missing.map((t) => ({ + type: "tool_result", + tool_use_id: t.id, + content: "Tool execution was interrupted.", + })) + next.content = [...repairs, ...nextContent] + } +} +``` + +### Fix 2: Ensure abort saves partial tool_results + +At [`Task.ts:3722`](references/Roo-Code/src/core/task/Task.ts:3722), before returning: + +```ts +} catch (error) { + // Save any accumulated tool_results to prevent orphaned tool_use blocks + if (this.userMessageContent.length > 0) { + await this.flushPendingToolResultsToHistory() + } + return true +} +``` + +### Fix 3: Detect and break the infinite retry loop + +In `attemptApiRequest`'s error handler, detect this specific Anthropic error pattern and auto-repair: + +```ts +if (error.message?.includes("tool_use` ids were found without `tool_result`")) { + await this.repairOrphanedToolUseBlocks() + yield * this.attemptApiRequest(retryAttempt + 1) + return +} +``` + +--- + +## Summary + +| Layer | What happens | File:Line | +| ----------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| **Trigger** | User cancels task, or network drops, or abort signal fires | `Task.ts:62-64` | +| **Corruption** | Assistant message (with `tool_use`) already saved, tool execution interrupted before `tool_result` saved | `Task.ts:3542` (save) → `Task.ts:3561` (execute) → abort before `Task.ts:2651` (save results) | +| **Missing guard** | `presentAssistantMessage` silently drops tool_results when `AskIgnoredError` or abort occurs | `presentAssistantMessage.ts:225`, `543` | +| **No recovery** | `validateAndFixToolResultIds` only runs at write-time, not pre-flight | `Task.ts:1016` | +| **Infinite loop** | `attemptApiRequest` retries with same corrupted history | `Task.ts:4337` | +| **No escape** | User must start a new session; no "repair history" option exists | — | diff --git a/packages/app/src/index.css b/packages/app/src/index.css deleted file mode 100644 index defc7d1f6eb7..000000000000 --- a/packages/app/src/index.css +++ /dev/null @@ -1,564 +0,0 @@ -@import "@opencode/ui/styles/tailwind"; -@import "@opencode/session-ui/styles"; -@import "@opencode/ui/styles/tokens"; -@import "tw-animate-css"; - -@font-face { - font-family: "JetBrainsMono Nerd Font Mono"; - src: url("/assets/JetBrainsMonoNerdFontMono-Regular.woff2") format("woff2"); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: "Inter"; - src: url("/assets/Inter.ttf") format("truetype"); - font-weight: 100 900; - font-style: normal; -} - -@font-face { - font-family: "IBM Plex Mono"; - src: url("@ibm/plex/IBM-Plex-Mono/fonts/complete/woff2/IBMPlexMono-Text.woff2") format("woff2"); - font-weight: 440; - font-style: normal; - font-display: swap; -} - -@media (display-mode: standalone) { - /* WebKit excludes safe-area insets from dvh in installed apps. */ - #root { - height: 100vh; - } -} - -/* iOS can report navigator.standalone without matching display-mode. */ -#root[data-standalone="true"] { - height: 100vh; -} - -@layer components { - [data-slot="session-chat-panel"][data-scrollbar-hidden="true"] - [data-slot="session-timeline-scroll"] - > .scroll-view__thumb { - visibility: hidden; - } - - [data-slot="session-side-panel-presence"][data-opened="true"] { - animation: side-region-presence-in 240ms cubic-bezier(0.22, 1, 0.36, 1); - } - - [data-slot="session-side-panel-presence"][data-opened="false"] { - animation: side-region-presence-out 240ms cubic-bezier(0.22, 1, 0.36, 1) forwards; - } - - #review-panel { - container-type: inline-size; - } - - /* Like the composer toolbar, fade only clipped content without reserving layout space. - The fade contracts as overflow clears; the second mask preserves the header divider. */ - #review-panel .session-review-v2-tabs-bar { - --session-review-header-fade: clamp(0px, calc(100% - 100cqi), 24px); - mask-image: - linear-gradient( - to right, - #000 calc(100cqi - 40px - var(--session-review-header-fade)), - transparent calc(100cqi - 40px) - ), - linear-gradient(to top, #000 1px, transparent 1px); - - &:dir(rtl) { - mask-image: - linear-gradient( - to left, - #000 calc(100cqi - 40px - var(--session-review-header-fade)), - transparent calc(100cqi - 40px) - ), - linear-gradient(to top, #000 1px, transparent 1px); - } - } - - /* The panel's width animation supplies the slide; only fade its fixed-width contents. */ - [data-slot="session-side-region-presence"][data-opened] [data-slot="session-review-content"] { - transition: opacity 200ms ease-out 40ms; - } - - /* Cached contents must stay transparent even after the presence animation finishes. */ - #review-panel[aria-hidden="true"] > [data-slot="session-review-content"] { - opacity: 0; - } - - [data-slot="session-side-region-presence"][data-opened="false"] [data-slot="session-review-content"] { - transition: opacity 160ms ease-out; - } - - @starting-style { - [data-slot="session-side-region-presence"][data-opened="true"] [data-slot="session-review-content"] { - opacity: 0; - } - } - - [data-slot="session-side-region-presence"][data-opened="true"] { - animation: side-region-presence-in 240ms cubic-bezier(0.22, 1, 0.36, 1); - } - - [data-slot="session-side-region-presence"][data-opened="false"] { - animation: side-region-presence-out 240ms cubic-bezier(0.22, 1, 0.36, 1) forwards; - } - - [data-slot="terminal-panel-presence"][data-opened="true"] { - animation: terminal-panel-presence-in 200ms cubic-bezier(0.22, 1, 0.36, 1); - } - - [data-slot="terminal-panel-presence"][data-opened="false"] { - animation: terminal-panel-presence-out 200ms cubic-bezier(0.22, 1, 0.36, 1) forwards; - } - - [data-slot="side-terminal-panel-presence"][data-opened="true"] { - animation: side-terminal-panel-presence-in 240ms cubic-bezier(0.22, 1, 0.36, 1); - } - - [data-slot="side-terminal-panel-presence"][data-opened="false"] { - animation: side-terminal-panel-presence-out 240ms cubic-bezier(0.22, 1, 0.36, 1) forwards; - } - - [data-component="terminal-panel"][data-size-animated="true"][data-opened="true"] { - animation: terminal-panel-size-in 200ms cubic-bezier(0.22, 1, 0.36, 1); - } - - [data-component="terminal-panel"][data-size-animated="true"][data-opened="false"] { - animation: terminal-panel-size-out 200ms cubic-bezier(0.22, 1, 0.36, 1); - } - - @media (prefers-reduced-motion: reduce) { - [data-slot="terminal-panel-presence"], - [data-slot="side-terminal-panel-presence"], - [data-slot="session-side-panel-presence"], - [data-slot="session-review-content"], - [data-slot="session-side-region-presence"], - [data-component="terminal-panel"] { - animation: none !important; - } - - [data-slot="session-review-content"] { - transition: none !important; - } - } - - @keyframes terminal-panel-presence-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - @keyframes terminal-panel-presence-out { - from { - opacity: 1; - } - to { - opacity: 0; - } - } - - @keyframes side-terminal-panel-presence-in { - from { - opacity: 0.999999; - } - to { - opacity: 1; - } - } - - @keyframes side-terminal-panel-presence-out { - from { - opacity: 1; - } - to { - opacity: 0.999999; - visibility: hidden; - } - } - - /* Presence needs an animation lifetime, but the panel frame must never fade. */ - @keyframes side-region-presence-in { - from, - to { - opacity: 1; - } - } - - @keyframes side-region-presence-out { - from { - opacity: 1; - } - to { - opacity: 1; - visibility: hidden; - } - } - - @keyframes terminal-panel-size-in { - from { - height: 0; - } - to { - height: var(--terminal-panel-height); - } - } - - @keyframes terminal-panel-size-out { - from { - height: var(--terminal-panel-height); - } - to { - height: 0; - } - } - - ::highlight(timeline-search-hit) { - background-color: color-mix(in srgb, var(--v2-icon-icon-accent) 28%, transparent); - } - - ::highlight(timeline-search-hit-active) { - background-color: var(--v2-icon-icon-accent); - color: var(--v2-background-bg-deep); - } - - [data-component="menu-v2-content"].desktop-app-menu { - min-width: 160px; - padding: 2px; - } - - [data-component="menu-v2-content"].desktop-app-menu:not(.desktop-app-menu-sub) { - width: 160px; - } - - [data-component="menu-v2-content"].desktop-app-menu-sub { - width: max-content; - min-width: 240px; - max-width: min(320px, calc(100vw - 24px)); - } - - [data-component="menu-v2-content"].desktop-app-menu [data-slot="menu-v2-group-label"] { - display: flex; - align-items: center; - height: 28px; - padding: 0 12px; - font-size: var(--font-size-x-small); - font-weight: var(--font-weight-medium); - line-height: 1; - color: var(--text-weak); - } - - [data-component="menu-v2-content"].desktop-app-menu [data-component="menu-v2-item"] { - min-height: 28px; - padding: 0 12px; - gap: 8px; - font-weight: var(--font-weight-regular); - line-height: 1; - } - - [data-component="menu-v2-content"].desktop-app-menu [data-slot="menu-v2-item-content"] { - white-space: nowrap; - } - - [data-component="menu-v2-content"].desktop-app-menu [data-slot="menu-v2-item-shortcut"] { - margin-left: auto; - color: var(--text-weak); - font-size: var(--font-size-x-small); - font-weight: var(--font-weight-regular); - white-space: nowrap; - } - - .home-session-group-header::before { - content: ""; - position: absolute; - top: -12px; - left: 0; - width: 100%; - height: 12px; - background: var(--v2-background-bg-base); - } - - .home-session-group-header::after { - content: ""; - position: absolute; - top: 100%; - left: 0; - width: 100%; - height: 16px; - pointer-events: none; - background: linear-gradient( - 180deg, - var(--v2-background-bg-base) 0%, - color-mix(in srgb, var(--v2-background-bg-base) 92.0456%, transparent) 7.93%, - color-mix(in srgb, var(--v2-background-bg-base) 84.9947%, transparent) 14.14%, - color-mix(in srgb, var(--v2-background-bg-base) 78.6813%, transparent) 19%, - color-mix(in srgb, var(--v2-background-bg-base) 72.9394%, transparent) 22.85%, - color-mix(in srgb, var(--v2-background-bg-base) 67.6028%, transparent) 26.05%, - color-mix(in srgb, var(--v2-background-bg-base) 62.5055%, transparent) 28.95%, - color-mix(in srgb, var(--v2-background-bg-base) 57.4815%, transparent) 31.91%, - color-mix(in srgb, var(--v2-background-bg-base) 52.3647%, transparent) 35.27%, - color-mix(in srgb, var(--v2-background-bg-base) 46.989%, transparent) 39.4%, - color-mix(in srgb, var(--v2-background-bg-base) 41.1884%, transparent) 44.65%, - color-mix(in srgb, var(--v2-background-bg-base) 34.7969%, transparent) 51.36%, - color-mix(in srgb, var(--v2-background-bg-base) 27.6484%, transparent) 59.9%, - color-mix(in srgb, var(--v2-background-bg-base) 19.5767%, transparent) 70.62%, - color-mix(in srgb, var(--v2-background-bg-base) 10.416%, transparent) 83.87%, - transparent 100% - ); - } - - [data-slot="titlebar-update-loader"] { - display: block; - flex-shrink: 0; - margin: 1px; - width: 12px; - height: 12px; - border-radius: 9999px; - border: 2px solid color-mix(in srgb, var(--v2-icon-icon-accent) 30%, transparent); - border-top-color: var(--v2-icon-icon-accent); - animation: titlebar-update-loader-spin 0.67s linear infinite; - transform-origin: 50% 50%; - } - - @media (prefers-reduced-motion: reduce) { - [data-slot="titlebar-update-loader"] { - animation: none; - } - } - - @keyframes titlebar-update-loader-spin { - to { - transform: rotate(360deg); - } - } - - @keyframes home-projects-fade-top { - from { - visibility: hidden; - } - to { - visibility: visible; - } - } - - @keyframes home-projects-fade-bottom { - from { - visibility: visible; - } - to { - visibility: hidden; - } - } - - @keyframes session-dropzone-enter { - from { - opacity: 0; - } - } - - @keyframes session-dropzone-content-enter { - from { - opacity: 0; - transform: translateY(4px) scale(0.96); - } - } - - @keyframes session-dropzone-upload-enter { - from { - opacity: 0; - transform: translateY(4px) scale(0.92); - } - } - - @keyframes session-dropzone-stripes-drift { - to { - background-position: 33.941px 0; - } - } - - [data-component="session-dropzone"][data-visible="true"] { - animation: session-dropzone-enter 200ms cubic-bezier(0.215, 0.61, 0.355, 1); - } - - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-content"] { - animation: session-dropzone-content-enter 200ms cubic-bezier(0.215, 0.61, 0.355, 1); - } - - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-upload"] { - animation: session-dropzone-upload-enter 240ms cubic-bezier(0.215, 0.61, 0.355, 1); - } - - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-stripes"] { - animation: session-dropzone-stripes-drift 3s linear infinite; - } - - [data-component="session-dropzone"] { - --session-dropzone-wash: 4%; - --session-dropzone-stripe: 7%; - --session-dropzone-card: var(--v2-background-bg-base); - } - - [data-color-scheme="dark"] [data-component="session-dropzone"] { - --session-dropzone-wash: 6%; - --session-dropzone-stripe: 14%; - --session-dropzone-card: var(--v2-background-bg-layer-01); - } - - @media (prefers-reduced-motion: reduce) { - [data-component="session-dropzone"][data-visible="true"], - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-content"], - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-upload"], - [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-stripes"] { - animation: none; - } - } - - [data-slot="home-projects-scroll"] { - timeline-scope: --home-projects-scroll; - } - - [data-slot="model-selector-scroll"] { - timeline-scope: --model-selector-scroll; - } - - [data-slot="manage-models-scroll"] { - timeline-scope: --manage-models-scroll; - } - - [data-slot="home-projects-scroll"]::before, - [data-slot="home-projects-scroll"]::after, - [data-slot="model-selector-scroll"]::before, - [data-slot="model-selector-scroll"]::after, - [data-slot="manage-models-scroll"]::before { - content: ""; - position: absolute; - left: 0; - right: 0; - z-index: 10; - height: 16px; - pointer-events: none; - visibility: hidden; - } - - [data-slot="home-projects-scroll"]::before { - top: 0; - background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent); - } - - [data-slot="model-selector-scroll"]::before { - top: 0; - height: 12px; - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100% - ); - } - - [data-slot="manage-models-scroll"]::before { - top: 0; - background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent); - } - - [data-slot="home-projects-scroll"]::after { - bottom: 0; - background: linear-gradient(to top, var(--v2-background-bg-base), transparent); - } - - [data-slot="model-selector-scroll"]::after { - bottom: 0; - height: 12px; - background: linear-gradient( - 0deg, - color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%, - color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100% - ); - } - - @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) { - [data-slot="home-projects-scroll"] .scroll-view__viewport { - scroll-timeline: --home-projects-scroll y; - } - - [data-slot="home-projects-scroll"]::before { - animation: home-projects-fade-top linear both; - animation-timeline: --home-projects-scroll; - animation-range: 0 0.1px; - } - - [data-slot="home-projects-scroll"]::after { - animation: home-projects-fade-bottom linear both; - animation-timeline: --home-projects-scroll; - animation-range: calc(100% - 1.1px) calc(100% - 1px); - } - } - - @supports (animation-timeline: --model-selector-scroll) and (timeline-scope: --model-selector-scroll) { - [data-slot="model-selector-scroll"] .scroll-view__viewport { - scroll-timeline: --model-selector-scroll y; - } - - [data-slot="model-selector-scroll"]::before { - animation: home-projects-fade-top linear both; - animation-timeline: --model-selector-scroll; - animation-range: 0 0.1px; - } - - [data-slot="model-selector-scroll"]::after { - animation: home-projects-fade-bottom linear both; - animation-timeline: --model-selector-scroll; - animation-range: calc(100% - 1.1px) calc(100% - 1px); - } - } - - @supports (animation-timeline: --manage-models-scroll) and (timeline-scope: --manage-models-scroll) { - [data-slot="manage-models-scroll"] .settings-panel { - scroll-timeline: --manage-models-scroll y; - } - - [data-slot="manage-models-scroll"]::before { - animation: home-projects-fade-top linear both; - animation-timeline: --manage-models-scroll; - animation-range: 0 0.1px; - } - } - - :root body [data-slot="session-turn-diffs-header"] { - height: 24px; - padding-block: 0; - } -} diff --git a/packages/app/src/providers/custom-managed-provider.svg b/packages/app/src/providers/custom-managed-provider.svg index 5876403128cd..01a6e5b5a3a8 100644 --- a/packages/app/src/providers/custom-managed-provider.svg +++ b/packages/app/src/providers/custom-managed-provider.svg @@ -1,31 +1 @@ - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - -
+
\ No newline at end of file diff --git a/packages/app/src/settings/about/legal.svg b/packages/app/src/settings/about/legal.svg index e80987f08f68..c6291a96c909 100644 --- a/packages/app/src/settings/about/legal.svg +++ b/packages/app/src/settings/about/legal.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg b/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg index 6a67f62717b1..a39c165cedcd 100644 --- a/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg +++ b/packages/console/app/src/asset/brand/opencode-logo-dark-square.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-logo-dark.svg b/packages/console/app/src/asset/brand/opencode-logo-dark.svg index c28babff1be1..790fbc494fe5 100644 --- a/packages/console/app/src/asset/brand/opencode-logo-dark.svg +++ b/packages/console/app/src/asset/brand/opencode-logo-dark.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-logo-light-square.svg b/packages/console/app/src/asset/brand/opencode-logo-light-square.svg index a738ad87dbb5..5c45eae60ea5 100644 --- a/packages/console/app/src/asset/brand/opencode-logo-light-square.svg +++ b/packages/console/app/src/asset/brand/opencode-logo-light-square.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-logo-light.svg b/packages/console/app/src/asset/brand/opencode-logo-light.svg index 7ed0af003bb6..5d4a01a49b53 100644 --- a/packages/console/app/src/asset/brand/opencode-logo-light.svg +++ b/packages/console/app/src/asset/brand/opencode-logo-light.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-wordmark-dark.svg b/packages/console/app/src/asset/brand/opencode-wordmark-dark.svg index a242eeeab126..8fef01609245 100644 --- a/packages/console/app/src/asset/brand/opencode-wordmark-dark.svg +++ b/packages/console/app/src/asset/brand/opencode-wordmark-dark.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-wordmark-light.svg b/packages/console/app/src/asset/brand/opencode-wordmark-light.svg index 24a36c7ce7fa..c970f55a3193 100644 --- a/packages/console/app/src/asset/brand/opencode-wordmark-light.svg +++ b/packages/console/app/src/asset/brand/opencode-wordmark-light.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-wordmark-simple-dark.svg b/packages/console/app/src/asset/brand/opencode-wordmark-simple-dark.svg index afc323e4d558..041d9c0f3643 100644 --- a/packages/console/app/src/asset/brand/opencode-wordmark-simple-dark.svg +++ b/packages/console/app/src/asset/brand/opencode-wordmark-simple-dark.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/brand/opencode-wordmark-simple-light.svg b/packages/console/app/src/asset/brand/opencode-wordmark-simple-light.svg index 29be24534d26..ce06c9ae2ce4 100644 --- a/packages/console/app/src/asset/brand/opencode-wordmark-simple-light.svg +++ b/packages/console/app/src/asset/brand/opencode-wordmark-simple-light.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/brand-assets-dark.svg b/packages/console/app/src/asset/lander/brand-assets-dark.svg index 93da2462d9eb..6dea57d9a0b6 100644 --- a/packages/console/app/src/asset/lander/brand-assets-dark.svg +++ b/packages/console/app/src/asset/lander/brand-assets-dark.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/brand-assets-light.svg b/packages/console/app/src/asset/lander/brand-assets-light.svg index aa9d115bfc97..ebb2556f133f 100644 --- a/packages/console/app/src/asset/lander/brand-assets-light.svg +++ b/packages/console/app/src/asset/lander/brand-assets-light.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/check.svg b/packages/console/app/src/asset/lander/check.svg index 0ac7759ea56c..0a68876455e2 100644 --- a/packages/console/app/src/asset/lander/check.svg +++ b/packages/console/app/src/asset/lander/check.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/copy.svg b/packages/console/app/src/asset/lander/copy.svg index e2263279e5ea..727e5ff384f5 100644 --- a/packages/console/app/src/asset/lander/copy.svg +++ b/packages/console/app/src/asset/lander/copy.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/logo-dark.svg b/packages/console/app/src/asset/lander/logo-dark.svg index d73830f9313d..fc80cbf6a4e6 100644 --- a/packages/console/app/src/asset/lander/logo-dark.svg +++ b/packages/console/app/src/asset/lander/logo-dark.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/logo-light.svg b/packages/console/app/src/asset/lander/logo-light.svg index 7394bf432566..aa2f76eba5d0 100644 --- a/packages/console/app/src/asset/lander/logo-light.svg +++ b/packages/console/app/src/asset/lander/logo-light.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/opencode-logo-dark.svg b/packages/console/app/src/asset/lander/opencode-logo-dark.svg index 154000aaa585..07efb360eba0 100644 --- a/packages/console/app/src/asset/lander/opencode-logo-dark.svg +++ b/packages/console/app/src/asset/lander/opencode-logo-dark.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/opencode-logo-light.svg b/packages/console/app/src/asset/lander/opencode-logo-light.svg index c1259a77def3..801d081e60dd 100644 --- a/packages/console/app/src/asset/lander/opencode-logo-light.svg +++ b/packages/console/app/src/asset/lander/opencode-logo-light.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/opencode-wordmark-dark.svg b/packages/console/app/src/asset/lander/opencode-wordmark-dark.svg index 822d971ad8e1..f8ab70772c95 100644 --- a/packages/console/app/src/asset/lander/opencode-wordmark-dark.svg +++ b/packages/console/app/src/asset/lander/opencode-wordmark-dark.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/opencode-wordmark-light.svg b/packages/console/app/src/asset/lander/opencode-wordmark-light.svg index 6d98af7004f5..b18291d5ea64 100644 --- a/packages/console/app/src/asset/lander/opencode-wordmark-light.svg +++ b/packages/console/app/src/asset/lander/opencode-wordmark-light.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/wordmark-dark.svg b/packages/console/app/src/asset/lander/wordmark-dark.svg index 42f8e22a6dc7..aee42d41b16f 100644 --- a/packages/console/app/src/asset/lander/wordmark-dark.svg +++ b/packages/console/app/src/asset/lander/wordmark-dark.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/lander/wordmark-light.svg b/packages/console/app/src/asset/lander/wordmark-light.svg index 398278da6906..a81d5bb59408 100644 --- a/packages/console/app/src/asset/lander/wordmark-light.svg +++ b/packages/console/app/src/asset/lander/wordmark-light.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/logo-ornate-dark.svg b/packages/console/app/src/asset/logo-ornate-dark.svg index a1582732423a..9c8e076d266c 100644 --- a/packages/console/app/src/asset/logo-ornate-dark.svg +++ b/packages/console/app/src/asset/logo-ornate-dark.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/logo-ornate-light.svg b/packages/console/app/src/asset/logo-ornate-light.svg index 2a856dccefe8..6e3e80c6a2ed 100644 --- a/packages/console/app/src/asset/logo-ornate-light.svg +++ b/packages/console/app/src/asset/logo-ornate-light.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/logo.svg b/packages/console/app/src/asset/logo.svg index 2a856dccefe8..6e3e80c6a2ed 100644 --- a/packages/console/app/src/asset/logo.svg +++ b/packages/console/app/src/asset/logo.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/zen-ornate-dark.svg b/packages/console/app/src/asset/zen-ornate-dark.svg index cdc4485fc59d..23b500f8106c 100644 --- a/packages/console/app/src/asset/zen-ornate-dark.svg +++ b/packages/console/app/src/asset/zen-ornate-dark.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/console/app/src/asset/zen-ornate-light.svg b/packages/console/app/src/asset/zen-ornate-light.svg index 2a9ed13421e3..45e35dcbaf66 100644 --- a/packages/console/app/src/asset/zen-ornate-light.svg +++ b/packages/console/app/src/asset/zen-ornate-light.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/identity/mark-light.svg b/packages/identity/mark-light.svg index ac619f1b2ff2..19a2b29408e0 100644 --- a/packages/identity/mark-light.svg +++ b/packages/identity/mark-light.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/identity/mark.svg b/packages/identity/mark.svg index 157edc4d7522..7e00cac5fafb 100644 --- a/packages/identity/mark.svg +++ b/packages/identity/mark.svg @@ -1,7 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/opencode/src/session/steer.ts b/packages/opencode/src/session/steer.ts new file mode 100644 index 000000000000..5de01facc86b --- /dev/null +++ b/packages/opencode/src/session/steer.ts @@ -0,0 +1,127 @@ +import { Bus } from "../bus" +import { BusEvent } from "../bus/bus-event" +import { Instance } from "../project/instance" +import { Log } from "../util/log" +import z from "zod" + +export namespace SessionSteer { + const log = Log.create({ service: "session.steer" }) + + export type Mode = "queue" | "steer" + + const QueuedMessageSchema = z.object({ + id: z.string(), + text: z.string(), + time: z.number(), + mode: z.enum(["queue", "steer"]), + }) + + export const Event = { + QueueChanged: BusEvent.define( + "session.queue.changed", + z.object({ + sessionID: z.string(), + queue: z.array(QueuedMessageSchema), + }), + ), + } + + export interface QueuedMessage { + id: string + text: string + time: number + mode: Mode + } + + interface SteerState { + pending: QueuedMessage[] + } + + const state = Instance.state( + () => { + const data: Record = {} + return data + }, + async () => {}, + ) + + function ensure(sessionID: string): SteerState { + const s = state() + if (!s[sessionID]) s[sessionID] = { pending: [] } + return s[sessionID] + } + + /** Push a message into the pending buffer for an active session. */ + export function push(sessionID: string, text: string, mode: Mode = "queue"): QueuedMessage { + const entry: QueuedMessage = { + id: crypto.randomUUID(), + text, + time: Date.now(), + mode, + } + const s = ensure(sessionID) + s.pending.push(entry) + log.info("steer.push", { sessionID, id: entry.id, queueLength: s.pending.length }) + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + return entry + } + + /** Drain all pending messages and return them. Clears the buffer. */ + export function take(sessionID: string): QueuedMessage[] { + const s = state()[sessionID] + if (!s || s.pending.length === 0) return [] + const result = s.pending.splice(0) + log.info("steer.take", { sessionID, count: result.length }) + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + return result + } + + /** Drain only messages matching the given mode. Leaves other messages in the buffer. */ + export function takeByMode(sessionID: string, mode: Mode): QueuedMessage[] { + const s = state()[sessionID] + if (!s || s.pending.length === 0) return [] + const matched: QueuedMessage[] = [] + const remaining: QueuedMessage[] = [] + for (const m of s.pending) { + if (m.mode === mode) matched.push(m) + else remaining.push(m) + } + if (matched.length === 0) return [] + s.pending = remaining + log.info("steer.takeByMode", { sessionID, mode, count: matched.length }) + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + return matched + } + + /** Check if there's pending steered input for a session. */ + export function has(sessionID: string): boolean { + const s = state()[sessionID] + return !!s && s.pending.length > 0 + } + + /** Get the current queue without draining. */ + export function list(sessionID: string): QueuedMessage[] { + return state()[sessionID]?.pending ?? [] + } + + /** Remove a specific queued message by id. */ + export function remove(sessionID: string, id: string): boolean { + const s = state()[sessionID] + if (!s) return false + const idx = s.pending.findIndex((m) => m.id === id) + if (idx === -1) return false + s.pending.splice(idx, 1) + log.info("steer.remove", { sessionID, id }) + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + return true + } + + /** Clear all pending messages for a session. */ + export function clear(sessionID: string) { + const s = state()[sessionID] + if (!s || s.pending.length === 0) return + s.pending.length = 0 + log.info("steer.clear", { sessionID }) + Bus.publish(Event.QueueChanged, { sessionID, queue: s.pending }) + } +} diff --git a/packages/opencode/test/provider/error.test.ts b/packages/opencode/test/provider/error.test.ts new file mode 100644 index 000000000000..e0b4afb45d6e --- /dev/null +++ b/packages/opencode/test/provider/error.test.ts @@ -0,0 +1,242 @@ +import { describe, expect, test } from "bun:test" +import { APICallError } from "ai" +import { ProviderError } from "../../src/provider/error" + +function makeAPICallError(opts: { + message: string + statusCode?: number + responseBody?: string + isRetryable?: boolean + responseHeaders?: Record +}) { + return new APICallError({ + message: opts.message, + url: "https://bedrock.us-east-1.amazonaws.com", + requestBodyValues: {}, + statusCode: opts.statusCode ?? 400, + responseHeaders: opts.responseHeaders ?? {}, + responseBody: opts.responseBody, + isRetryable: opts.isRetryable ?? false, + }) +} + +describe("provider.error.parseAPICallError", () => { + // Issue #2: Bedrock "undefined" message handling + test("detects overflow when message is literal 'undefined' and responseBody has prompt-too-long", () => { + const error = makeAPICallError({ + message: "undefined", + statusCode: 400, + responseBody: JSON.stringify({ + message: "prompt is too long: 208845 tokens > 200000 maximum", + }), + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + test("detects overflow when message is empty string and responseBody has prompt-too-long", () => { + const error = makeAPICallError({ + message: "", + statusCode: 400, + responseBody: JSON.stringify({ + message: "prompt is too long: 208845 tokens > 200000 maximum", + }), + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + test("does NOT treat literal 'undefined' as overflow when responseBody has no overflow pattern", () => { + const error = makeAPICallError({ + message: "undefined", + statusCode: 403, + responseBody: JSON.stringify({ message: "Access denied" }), + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("api_error") + }) + + test("falls back to statusCode text when message is 'undefined' and no responseBody", () => { + const error = makeAPICallError({ + message: "undefined", + statusCode: 429, + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("api_error") + if (result.type === "api_error") { + expect(result.message).toBe("Too Many Requests") + } + }) + + test("returns 'Unknown error' when message is 'undefined' and no responseBody or statusCode", () => { + const error = new APICallError({ + message: "undefined", + url: "https://test", + requestBodyValues: {}, + responseHeaders: {}, + isRetryable: false, + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("api_error") + if (result.type === "api_error") { + expect(result.message).toBe("Unknown error") + } + }) + + // Overflow patterns across providers + test("detects overflow for direct Anthropic provider with normal message", () => { + const error = makeAPICallError({ + message: "prompt is too long: 208845 tokens > 200000 maximum", + statusCode: 400, + }) + const result = ProviderError.parseAPICallError({ + providerID: "anthropic", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + test("detects overflow for Bedrock 'input is too long' pattern", () => { + const error = makeAPICallError({ + message: "undefined", + statusCode: 400, + responseBody: "input is too long for requested model", + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + test("detects overflow for OpenAI 'exceeds the context window' pattern", () => { + const error = makeAPICallError({ + message: "This model's maximum context length exceeds the context window", + statusCode: 400, + }) + const result = ProviderError.parseAPICallError({ + providerID: "openai", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + test("detects overflow for generic 'context_length_exceeded' pattern", () => { + const error = makeAPICallError({ + message: "context_length_exceeded", + statusCode: 400, + }) + const result = ProviderError.parseAPICallError({ + providerID: "openrouter", + error, + }) + expect(result.type).toBe("context_overflow") + }) + + // Non-overflow API errors + test("returns api_error for rate limit errors", () => { + const error = makeAPICallError({ + message: "Rate limit exceeded", + statusCode: 429, + isRetryable: true, + }) + const result = ProviderError.parseAPICallError({ + providerID: "amazon-bedrock", + error, + }) + expect(result.type).toBe("api_error") + if (result.type === "api_error") { + expect(result.isRetryable).toBe(true) + expect(result.statusCode).toBe(429) + } + }) + + test("returns api_error with isRetryable false for auth errors", () => { + const error = makeAPICallError({ + message: "Invalid API key", + statusCode: 401, + isRetryable: false, + }) + const result = ProviderError.parseAPICallError({ + providerID: "anthropic", + error, + }) + expect(result.type).toBe("api_error") + if (result.type === "api_error") { + expect(result.isRetryable).toBe(false) + } + }) + + test("includes url metadata when error has url", () => { + const error = makeAPICallError({ + message: "Server error", + statusCode: 500, + isRetryable: true, + }) + const result = ProviderError.parseAPICallError({ + providerID: "anthropic", + error, + }) + expect(result.type).toBe("api_error") + if (result.type === "api_error") { + expect(result.metadata).toBeDefined() + } + }) +}) + +describe("provider.error.parseStreamError", () => { + test("detects context_length_exceeded from stream error", () => { + const result = ProviderError.parseStreamError({ + type: "error", + error: { code: "context_length_exceeded" }, + }) + expect(result).toBeDefined() + expect(result!.type).toBe("context_overflow") + }) + + test("detects insufficient_quota from stream error", () => { + const result = ProviderError.parseStreamError({ + type: "error", + error: { code: "insufficient_quota" }, + }) + expect(result).toBeDefined() + expect(result!.type).toBe("api_error") + if (result!.type === "api_error") { + expect(result!.isRetryable).toBe(false) + } + }) + + test("returns undefined for non-error stream events", () => { + const result = ProviderError.parseStreamError({ type: "data", content: "hello" }) + expect(result).toBeUndefined() + }) + + test("returns undefined for non-object input", () => { + expect(ProviderError.parseStreamError("string")).toBeUndefined() + expect(ProviderError.parseStreamError(42)).toBeUndefined() + expect(ProviderError.parseStreamError(null)).toBeUndefined() + }) + + test("parses JSON string input", () => { + const result = ProviderError.parseStreamError( + JSON.stringify({ type: "error", error: { code: "context_length_exceeded" } }), + ) + expect(result).toBeDefined() + expect(result!.type).toBe("context_overflow") + }) +}) diff --git a/packages/opencode/test/session/steer.test.ts b/packages/opencode/test/session/steer.test.ts new file mode 100644 index 000000000000..5e4e8e8639a9 --- /dev/null +++ b/packages/opencode/test/session/steer.test.ts @@ -0,0 +1,235 @@ +import { describe, expect, test, beforeEach } from "bun:test" +import path from "path" +import { SessionSteer } from "../../src/session/steer" +import { Instance } from "../../src/project/instance" +import { Log } from "../../src/util/log" + +const projectRoot = path.join(__dirname, "../..") +const SESSION = "session_test_steer_001" +Log.init({ print: false }) + +/** Helper to run a test function inside Instance.provide context */ +function withInstance(fn: () => void | Promise) { + return Instance.provide({ + directory: projectRoot, + fn: async () => { + await fn() + }, + }) +} + +describe("SessionSteer", () => { + describe("push", () => { + test("creates a queued message with default mode 'queue'", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + const msg = SessionSteer.push(SESSION, "hello") + expect(msg.text).toBe("hello") + expect(msg.mode).toBe("queue") + expect(msg.id).toBeTruthy() + expect(msg.time).toBeGreaterThan(0) + }) + }) + + test("accepts explicit mode 'steer'", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + const msg = SessionSteer.push(SESSION, "redirect", "steer") + expect(msg.text).toBe("redirect") + expect(msg.mode).toBe("steer") + }) + }) + + test("accepts explicit mode 'queue'", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + const msg = SessionSteer.push(SESSION, "later", "queue") + expect(msg.mode).toBe("queue") + }) + }) + }) + + describe("take", () => { + test("drains all messages regardless of mode", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "a", "queue") + SessionSteer.push(SESSION, "b", "steer") + SessionSteer.push(SESSION, "c", "queue") + + const taken = SessionSteer.take(SESSION) + expect(taken).toHaveLength(3) + expect(taken.map((m) => m.text)).toEqual(["a", "b", "c"]) + expect(SessionSteer.list(SESSION)).toHaveLength(0) + }) + }) + + test("returns empty array when no messages", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + expect(SessionSteer.take(SESSION)).toEqual([]) + }) + }) + }) + + describe("takeByMode", () => { + test("drains only 'steer' messages, leaving 'queue' messages", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "queued-1", "queue") + SessionSteer.push(SESSION, "steer-1", "steer") + SessionSteer.push(SESSION, "queued-2", "queue") + SessionSteer.push(SESSION, "steer-2", "steer") + + const steered = SessionSteer.takeByMode(SESSION, "steer") + expect(steered).toHaveLength(2) + expect(steered.map((m) => m.text)).toEqual(["steer-1", "steer-2"]) + + const remaining = SessionSteer.list(SESSION) + expect(remaining).toHaveLength(2) + expect(remaining.map((m) => m.text)).toEqual(["queued-1", "queued-2"]) + }) + }) + + test("drains only 'queue' messages, leaving 'steer' messages", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "queued-1", "queue") + SessionSteer.push(SESSION, "steer-1", "steer") + SessionSteer.push(SESSION, "queued-2", "queue") + + const queued = SessionSteer.takeByMode(SESSION, "queue") + expect(queued).toHaveLength(2) + expect(queued.map((m) => m.text)).toEqual(["queued-1", "queued-2"]) + + const remaining = SessionSteer.list(SESSION) + expect(remaining).toHaveLength(1) + expect(remaining[0].text).toBe("steer-1") + }) + }) + + test("returns empty when no messages match mode", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "queued", "queue") + const steered = SessionSteer.takeByMode(SESSION, "steer") + expect(steered).toEqual([]) + expect(SessionSteer.list(SESSION)).toHaveLength(1) + }) + }) + + test("returns empty when buffer is empty", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + expect(SessionSteer.takeByMode(SESSION, "steer")).toEqual([]) + expect(SessionSteer.takeByMode(SESSION, "queue")).toEqual([]) + }) + }) + + test("sequential takeByMode drains both modes completely", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "s1", "steer") + SessionSteer.push(SESSION, "q1", "queue") + SessionSteer.push(SESSION, "s2", "steer") + SessionSteer.push(SESSION, "q2", "queue") + + const steered = SessionSteer.takeByMode(SESSION, "steer") + expect(steered).toHaveLength(2) + + const queued = SessionSteer.takeByMode(SESSION, "queue") + expect(queued).toHaveLength(2) + + expect(SessionSteer.has(SESSION)).toBe(false) + expect(SessionSteer.list(SESSION)).toHaveLength(0) + }) + }) + }) + + describe("has", () => { + test("returns false for empty session", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + expect(SessionSteer.has(SESSION)).toBe(false) + }) + }) + + test("returns true after push", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "test") + expect(SessionSteer.has(SESSION)).toBe(true) + }) + }) + + test("returns false after take drains all", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "test") + SessionSteer.take(SESSION) + expect(SessionSteer.has(SESSION)).toBe(false) + }) + }) + + test("returns true when takeByMode leaves remaining", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "q", "queue") + SessionSteer.takeByMode(SESSION, "steer") + expect(SessionSteer.has(SESSION)).toBe(true) + }) + }) + }) + + describe("list", () => { + test("returns current queue without draining", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "a", "queue") + SessionSteer.push(SESSION, "b", "steer") + + const first = SessionSteer.list(SESSION) + expect(first).toHaveLength(2) + + const second = SessionSteer.list(SESSION) + expect(second).toHaveLength(2) + }) + }) + }) + + describe("remove", () => { + test("removes specific message by id", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + const msg = SessionSteer.push(SESSION, "target", "steer") + SessionSteer.push(SESSION, "keep", "queue") + + const removed = SessionSteer.remove(SESSION, msg.id) + expect(removed).toBe(true) + expect(SessionSteer.list(SESSION)).toHaveLength(1) + expect(SessionSteer.list(SESSION)[0].text).toBe("keep") + }) + }) + + test("returns false for non-existent id", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "test") + expect(SessionSteer.remove(SESSION, "nonexistent")).toBe(false) + }) + }) + }) + + describe("clear", () => { + test("removes all pending messages", async () => { + await withInstance(() => { + SessionSteer.clear(SESSION) + SessionSteer.push(SESSION, "a", "queue") + SessionSteer.push(SESSION, "b", "steer") + SessionSteer.clear(SESSION) + expect(SessionSteer.has(SESSION)).toBe(false) + expect(SessionSteer.list(SESSION)).toHaveLength(0) + }) + }) + }) +}) diff --git a/packages/posts/public/logo-dark.svg b/packages/posts/public/logo-dark.svg index 367289acc40a..bf6a39c9f376 100644 --- a/packages/posts/public/logo-dark.svg +++ b/packages/posts/public/logo-dark.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/session-ui/src/components/markdown-github.svg b/packages/session-ui/src/components/markdown-github.svg index e5181690f777..881b3f0f265f 100644 --- a/packages/session-ui/src/components/markdown-github.svg +++ b/packages/session-ui/src/components/markdown-github.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/session-ui/src/components/markdown-globe.svg b/packages/session-ui/src/components/markdown-globe.svg index 8773823cf208..16a78f8a0801 100644 --- a/packages/session-ui/src/components/markdown-globe.svg +++ b/packages/session-ui/src/components/markdown-globe.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/stats/app/src/asset/logo-ornate-dark.svg b/packages/stats/app/src/asset/logo-ornate-dark.svg index a1582732423a..9c8e076d266c 100644 --- a/packages/stats/app/src/asset/logo-ornate-dark.svg +++ b/packages/stats/app/src/asset/logo-ornate-dark.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/stats/app/src/asset/logo-ornate-light.svg b/packages/stats/app/src/asset/logo-ornate-light.svg index 2a856dccefe8..6e3e80c6a2ed 100644 --- a/packages/stats/app/src/asset/logo-ornate-light.svg +++ b/packages/stats/app/src/asset/logo-ornate-light.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/android-studio.svg b/packages/ui/src/assets/icons/app/android-studio.svg index 8d87619f754e..99072abbddaf 100644 --- a/packages/ui/src/assets/icons/app/android-studio.svg +++ b/packages/ui/src/assets/icons/app/android-studio.svg @@ -1,369 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/antigravity.svg b/packages/ui/src/assets/icons/app/antigravity.svg index 52f235068f0b..c24ea04d57e1 100644 --- a/packages/ui/src/assets/icons/app/antigravity.svg +++ b/packages/ui/src/assets/icons/app/antigravity.svg @@ -1,97 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/cursor.svg b/packages/ui/src/assets/icons/app/cursor.svg index a778608649d0..d434e642a8a3 100644 --- a/packages/ui/src/assets/icons/app/cursor.svg +++ b/packages/ui/src/assets/icons/app/cursor.svg @@ -1,16 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/file-explorer.svg b/packages/ui/src/assets/icons/app/file-explorer.svg index e2166579688c..1c491c1c7b78 100644 --- a/packages/ui/src/assets/icons/app/file-explorer.svg +++ b/packages/ui/src/assets/icons/app/file-explorer.svg @@ -1,20 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/ghostty.svg b/packages/ui/src/assets/icons/app/ghostty.svg index d646bd32b38b..ff273f8b4b14 100644 --- a/packages/ui/src/assets/icons/app/ghostty.svg +++ b/packages/ui/src/assets/icons/app/ghostty.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/iterm2.svg b/packages/ui/src/assets/icons/app/iterm2.svg index 48c1aac51f27..e6690e377a02 100644 --- a/packages/ui/src/assets/icons/app/iterm2.svg +++ b/packages/ui/src/assets/icons/app/iterm2.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/powershell.svg b/packages/ui/src/assets/icons/app/powershell.svg index 46570203c543..f7cedf5a1d08 100644 --- a/packages/ui/src/assets/icons/app/powershell.svg +++ b/packages/ui/src/assets/icons/app/powershell.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/sublimetext.svg b/packages/ui/src/assets/icons/app/sublimetext.svg index 7e625b559ed2..0bbda8d4a0fc 100644 --- a/packages/ui/src/assets/icons/app/sublimetext.svg +++ b/packages/ui/src/assets/icons/app/sublimetext.svg @@ -1,17 +1 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/vscode.svg b/packages/ui/src/assets/icons/app/vscode.svg index 6b1827a6a2e9..0cb5ad533a33 100644 --- a/packages/ui/src/assets/icons/app/vscode.svg +++ b/packages/ui/src/assets/icons/app/vscode.svg @@ -1,39 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/zed-dark.svg b/packages/ui/src/assets/icons/app/zed-dark.svg index 0f9d12d05f12..4cf2fe69e882 100644 --- a/packages/ui/src/assets/icons/app/zed-dark.svg +++ b/packages/ui/src/assets/icons/app/zed-dark.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/app/zed.svg b/packages/ui/src/assets/icons/app/zed.svg index 37aa2d6c09be..c0e201dae44e 100644 --- a/packages/ui/src/assets/icons/app/zed.svg +++ b/packages/ui/src/assets/icons/app/zed.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/auto_light.svg b/packages/ui/src/assets/icons/file-types/auto_light.svg index 5f2451b2384f..51d091821b2c 100644 --- a/packages/ui/src/assets/icons/file-types/auto_light.svg +++ b/packages/ui/src/assets/icons/file-types/auto_light.svg @@ -1,12 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cursor.svg b/packages/ui/src/assets/icons/file-types/cursor.svg index b754147ff0b2..a30ffe2bf01e 100644 --- a/packages/ui/src/assets/icons/file-types/cursor.svg +++ b/packages/ui/src/assets/icons/file-types/cursor.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/cursor_light.svg b/packages/ui/src/assets/icons/file-types/cursor_light.svg index f65b6461ae41..533fd3078528 100644 --- a/packages/ui/src/assets/icons/file-types/cursor_light.svg +++ b/packages/ui/src/assets/icons/file-types/cursor_light.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/drone_light.svg b/packages/ui/src/assets/icons/file-types/drone_light.svg index ce3ad253b47c..b1de6a290488 100644 --- a/packages/ui/src/assets/icons/file-types/drone_light.svg +++ b/packages/ui/src/assets/icons/file-types/drone_light.svg @@ -1,4 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-admin-open.svg b/packages/ui/src/assets/icons/file-types/folder-admin-open.svg index 5e77464ff8e0..acfda3b4b395 100644 --- a/packages/ui/src/assets/icons/file-types/folder-admin-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-admin-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-admin.svg b/packages/ui/src/assets/icons/file-types/folder-admin.svg index f8d1ea13c07a..c9dfa563f0a7 100644 --- a/packages/ui/src/assets/icons/file-types/folder-admin.svg +++ b/packages/ui/src/assets/icons/file-types/folder-admin.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg b/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg index 9e323ff9c289..2e90e8e7c4d5 100644 --- a/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-circleci-open.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-circleci.svg b/packages/ui/src/assets/icons/file-types/folder-circleci.svg index ef3251857948..4d82e2ee262d 100644 --- a/packages/ui/src/assets/icons/file-types/folder-circleci.svg +++ b/packages/ui/src/assets/icons/file-types/folder-circleci.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-flow-open.svg b/packages/ui/src/assets/icons/file-types/folder-flow-open.svg index a72dd76b7f22..5676f8f8e9bb 100644 --- a/packages/ui/src/assets/icons/file-types/folder-flow-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-flow-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-flow.svg b/packages/ui/src/assets/icons/file-types/folder-flow.svg index 015518922de3..5a48b820e86f 100644 --- a/packages/ui/src/assets/icons/file-types/folder-flow.svg +++ b/packages/ui/src/assets/icons/file-types/folder-flow.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg b/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg index 3ae400ed9f37..c41c21e07699 100644 --- a/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-gh-workflows-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg b/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg index 3a868cca97c5..561f758a1937 100644 --- a/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg +++ b/packages/ui/src/assets/icons/file-types/folder-gh-workflows.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-github-open.svg b/packages/ui/src/assets/icons/file-types/folder-github-open.svg index 84e5bee8497c..0cd9a59c15a9 100644 --- a/packages/ui/src/assets/icons/file-types/folder-github-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-github-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-github.svg b/packages/ui/src/assets/icons/file-types/folder-github.svg index 374bcae0b257..70b83b583910 100644 --- a/packages/ui/src/assets/icons/file-types/folder-github.svg +++ b/packages/ui/src/assets/icons/file-types/folder-github.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-intellij-open.svg b/packages/ui/src/assets/icons/file-types/folder-intellij-open.svg index 5839a2b1cc27..ed4d386f445b 100644 --- a/packages/ui/src/assets/icons/file-types/folder-intellij-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-intellij-open.svg @@ -1,39 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-intellij.svg b/packages/ui/src/assets/icons/file-types/folder-intellij.svg index c655f37ef1aa..5c50f70bed23 100644 --- a/packages/ui/src/assets/icons/file-types/folder-intellij.svg +++ b/packages/ui/src/assets/icons/file-types/folder-intellij.svg @@ -1,39 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-macos-open.svg b/packages/ui/src/assets/icons/file-types/folder-macos-open.svg index 8d0280ae902b..24e0e6434002 100644 --- a/packages/ui/src/assets/icons/file-types/folder-macos-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-macos-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-macos.svg b/packages/ui/src/assets/icons/file-types/folder-macos.svg index 6afe2ed2ae10..b2ae0d95f7d6 100644 --- a/packages/ui/src/assets/icons/file-types/folder-macos.svg +++ b/packages/ui/src/assets/icons/file-types/folder-macos.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-next-open.svg b/packages/ui/src/assets/icons/file-types/folder-next-open.svg index c8709cac308b..f88d0cdd73bb 100644 --- a/packages/ui/src/assets/icons/file-types/folder-next-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-next-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-next.svg b/packages/ui/src/assets/icons/file-types/folder-next.svg index cab1e8fca1a7..51d2fe5bf1ac 100644 --- a/packages/ui/src/assets/icons/file-types/folder-next.svg +++ b/packages/ui/src/assets/icons/file-types/folder-next.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-nuxt-open.svg b/packages/ui/src/assets/icons/file-types/folder-nuxt-open.svg index c49ff8d214e7..478f120400b8 100644 --- a/packages/ui/src/assets/icons/file-types/folder-nuxt-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-nuxt-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-nuxt.svg b/packages/ui/src/assets/icons/file-types/folder-nuxt.svg index a0a52b06ea74..83095bca3787 100644 --- a/packages/ui/src/assets/icons/file-types/folder-nuxt.svg +++ b/packages/ui/src/assets/icons/file-types/folder-nuxt.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-open.svg b/packages/ui/src/assets/icons/file-types/folder-open.svg index eac89185e84f..dc22934a92d6 100644 --- a/packages/ui/src/assets/icons/file-types/folder-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-open.svg @@ -1,5 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg b/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg index 981a43f783c1..74273cff9914 100644 --- a/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-scripts-open.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-scripts.svg b/packages/ui/src/assets/icons/file-types/folder-scripts.svg index 4b755acb9f88..fb524ec0e968 100644 --- a/packages/ui/src/assets/icons/file-types/folder-scripts.svg +++ b/packages/ui/src/assets/icons/file-types/folder-scripts.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-target-open.svg b/packages/ui/src/assets/icons/file-types/folder-target-open.svg index 0004bf8668aa..370d1be014b4 100644 --- a/packages/ui/src/assets/icons/file-types/folder-target-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-target-open.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-target.svg b/packages/ui/src/assets/icons/file-types/folder-target.svg index 5872750bd247..0b1ce42b2046 100644 --- a/packages/ui/src/assets/icons/file-types/folder-target.svg +++ b/packages/ui/src/assets/icons/file-types/folder-target.svg @@ -1,10 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg b/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg index e0d7c35cff48..ec401f2165ae 100644 --- a/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-turborepo-open.svg @@ -1,15 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-turborepo.svg b/packages/ui/src/assets/icons/file-types/folder-turborepo.svg index ea203360bd4b..7b87db3a75f1 100644 --- a/packages/ui/src/assets/icons/file-types/folder-turborepo.svg +++ b/packages/ui/src/assets/icons/file-types/folder-turborepo.svg @@ -1,15 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vercel-open.svg b/packages/ui/src/assets/icons/file-types/folder-vercel-open.svg index c571c63f3d66..a4ed0efcb39a 100644 --- a/packages/ui/src/assets/icons/file-types/folder-vercel-open.svg +++ b/packages/ui/src/assets/icons/file-types/folder-vercel-open.svg @@ -1,5 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder-vercel.svg b/packages/ui/src/assets/icons/file-types/folder-vercel.svg index 51384813030c..9b645651ff2a 100644 --- a/packages/ui/src/assets/icons/file-types/folder-vercel.svg +++ b/packages/ui/src/assets/icons/file-types/folder-vercel.svg @@ -1,5 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/folder.svg b/packages/ui/src/assets/icons/file-types/folder.svg index 97ee81ca4305..daa6894f52d6 100644 --- a/packages/ui/src/assets/icons/file-types/folder.svg +++ b/packages/ui/src/assets/icons/file-types/folder.svg @@ -1,5 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/go_gopher.svg b/packages/ui/src/assets/icons/file-types/go_gopher.svg index e465f7456137..7cb8682bb97b 100644 --- a/packages/ui/src/assets/icons/file-types/go_gopher.svg +++ b/packages/ui/src/assets/icons/file-types/go_gopher.svg @@ -1,35 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/nano-staged_light.svg b/packages/ui/src/assets/icons/file-types/nano-staged_light.svg index 698232f0c229..e00d768a9704 100644 --- a/packages/ui/src/assets/icons/file-types/nano-staged_light.svg +++ b/packages/ui/src/assets/icons/file-types/nano-staged_light.svg @@ -1,4 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/opa.svg b/packages/ui/src/assets/icons/file-types/opa.svg index 3afc1c6bf457..763492caa450 100644 --- a/packages/ui/src/assets/icons/file-types/opa.svg +++ b/packages/ui/src/assets/icons/file-types/opa.svg @@ -1,9 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/quarto.svg b/packages/ui/src/assets/icons/file-types/quarto.svg index 3bb8ef7c7a71..d09b4051f7b0 100644 --- a/packages/ui/src/assets/icons/file-types/quarto.svg +++ b/packages/ui/src/assets/icons/file-types/quarto.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/rome.svg b/packages/ui/src/assets/icons/file-types/rome.svg index 8f5de92d2892..0e270bbc8d67 100644 --- a/packages/ui/src/assets/icons/file-types/rome.svg +++ b/packages/ui/src/assets/icons/file-types/rome.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stitches_light.svg b/packages/ui/src/assets/icons/file-types/stitches_light.svg index 8001d9dfc9cd..5e34c97b299e 100644 --- a/packages/ui/src/assets/icons/file-types/stitches_light.svg +++ b/packages/ui/src/assets/icons/file-types/stitches_light.svg @@ -1,10 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/stylelint_light.svg b/packages/ui/src/assets/icons/file-types/stylelint_light.svg index 502fec33f14e..fd65ecea3727 100644 --- a/packages/ui/src/assets/icons/file-types/stylelint_light.svg +++ b/packages/ui/src/assets/icons/file-types/stylelint_light.svg @@ -1,13 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/unocss.svg b/packages/ui/src/assets/icons/file-types/unocss.svg index eab05c437963..5072d8a2eb31 100644 --- a/packages/ui/src/assets/icons/file-types/unocss.svg +++ b/packages/ui/src/assets/icons/file-types/unocss.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/file-types/vlang.svg b/packages/ui/src/assets/icons/file-types/vlang.svg index 17bf0e049c36..1d794ea72ddb 100644 --- a/packages/ui/src/assets/icons/file-types/vlang.svg +++ b/packages/ui/src/assets/icons/file-types/vlang.svg @@ -1,6 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/302ai.svg b/packages/ui/src/assets/icons/provider/302ai.svg index 46f2e4315e02..95bbaf6b2535 100644 --- a/packages/ui/src/assets/icons/provider/302ai.svg +++ b/packages/ui/src/assets/icons/provider/302ai.svg @@ -1,7 +1 @@ - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/abacus.svg b/packages/ui/src/assets/icons/provider/abacus.svg index 121a91d98ef0..13c195518df8 100644 --- a/packages/ui/src/assets/icons/provider/abacus.svg +++ b/packages/ui/src/assets/icons/provider/abacus.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/aihubmix.svg b/packages/ui/src/assets/icons/provider/aihubmix.svg index 33164b78b3e5..8f3555623d51 100644 --- a/packages/ui/src/assets/icons/provider/aihubmix.svg +++ b/packages/ui/src/assets/icons/provider/aihubmix.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/alibaba-cn.svg b/packages/ui/src/assets/icons/provider/alibaba-cn.svg index 5d8355c18e49..8f7b71fa8794 100644 --- a/packages/ui/src/assets/icons/provider/alibaba-cn.svg +++ b/packages/ui/src/assets/icons/provider/alibaba-cn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg b/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg index b3a2edc3c02a..95d098985d50 100644 --- a/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg +++ b/packages/ui/src/assets/icons/provider/alibaba-coding-plan-cn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg b/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg index b3a2edc3c02a..95d098985d50 100644 --- a/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/alibaba-coding-plan.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/alibaba.svg b/packages/ui/src/assets/icons/provider/alibaba.svg index b3a2edc3c02a..95d098985d50 100644 --- a/packages/ui/src/assets/icons/provider/alibaba.svg +++ b/packages/ui/src/assets/icons/provider/alibaba.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/amazon-bedrock.svg b/packages/ui/src/assets/icons/provider/amazon-bedrock.svg index 1f185ef53195..e744926ef616 100644 --- a/packages/ui/src/assets/icons/provider/amazon-bedrock.svg +++ b/packages/ui/src/assets/icons/provider/amazon-bedrock.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/anthropic.svg b/packages/ui/src/assets/icons/provider/anthropic.svg index aaa01fcdb2e4..2e516b785d43 100644 --- a/packages/ui/src/assets/icons/provider/anthropic.svg +++ b/packages/ui/src/assets/icons/provider/anthropic.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/azure-cognitive-services.svg b/packages/ui/src/assets/icons/provider/azure-cognitive-services.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/azure-cognitive-services.svg +++ b/packages/ui/src/assets/icons/provider/azure-cognitive-services.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/azure.svg b/packages/ui/src/assets/icons/provider/azure.svg index 07c6519ba4f3..8ac09549de11 100644 --- a/packages/ui/src/assets/icons/provider/azure.svg +++ b/packages/ui/src/assets/icons/provider/azure.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/bailing.svg b/packages/ui/src/assets/icons/provider/bailing.svg index b8ed486a86d1..db65ca21b622 100644 --- a/packages/ui/src/assets/icons/provider/bailing.svg +++ b/packages/ui/src/assets/icons/provider/bailing.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/baseten.svg b/packages/ui/src/assets/icons/provider/baseten.svg index ffd4fbd8b002..7ff53306fece 100644 --- a/packages/ui/src/assets/icons/provider/baseten.svg +++ b/packages/ui/src/assets/icons/provider/baseten.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/berget.svg b/packages/ui/src/assets/icons/provider/berget.svg index 831547a59ed0..ccef3abe8a79 100644 --- a/packages/ui/src/assets/icons/provider/berget.svg +++ b/packages/ui/src/assets/icons/provider/berget.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cerebras.svg b/packages/ui/src/assets/icons/provider/cerebras.svg index b167596729fe..8c697cfc9793 100644 --- a/packages/ui/src/assets/icons/provider/cerebras.svg +++ b/packages/ui/src/assets/icons/provider/cerebras.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/chutes.svg b/packages/ui/src/assets/icons/provider/chutes.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/chutes.svg +++ b/packages/ui/src/assets/icons/provider/chutes.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/clarifai.svg b/packages/ui/src/assets/icons/provider/clarifai.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/clarifai.svg +++ b/packages/ui/src/assets/icons/provider/clarifai.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg b/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg index 6f09a794e6ce..8171da2f8d2a 100644 --- a/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg +++ b/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cloudflare-ai-gateway.svg b/packages/ui/src/assets/icons/provider/cloudflare-ai-gateway.svg index 02c7e51d3e0d..3ce2c59b2e43 100644 --- a/packages/ui/src/assets/icons/provider/cloudflare-ai-gateway.svg +++ b/packages/ui/src/assets/icons/provider/cloudflare-ai-gateway.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cloudflare-workers-ai.svg b/packages/ui/src/assets/icons/provider/cloudflare-workers-ai.svg index 02c7e51d3e0d..3ce2c59b2e43 100644 --- a/packages/ui/src/assets/icons/provider/cloudflare-workers-ai.svg +++ b/packages/ui/src/assets/icons/provider/cloudflare-workers-ai.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cohere.svg b/packages/ui/src/assets/icons/provider/cohere.svg index cfeaa60028df..5299b6a50eac 100644 --- a/packages/ui/src/assets/icons/provider/cohere.svg +++ b/packages/ui/src/assets/icons/provider/cohere.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cortecs.svg b/packages/ui/src/assets/icons/provider/cortecs.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/cortecs.svg +++ b/packages/ui/src/assets/icons/provider/cortecs.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/deepinfra.svg b/packages/ui/src/assets/icons/provider/deepinfra.svg index c35ab7183c17..4816e71f53d9 100644 --- a/packages/ui/src/assets/icons/provider/deepinfra.svg +++ b/packages/ui/src/assets/icons/provider/deepinfra.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/deepseek.svg b/packages/ui/src/assets/icons/provider/deepseek.svg index 5d6efa991b7d..54844595d307 100644 --- a/packages/ui/src/assets/icons/provider/deepseek.svg +++ b/packages/ui/src/assets/icons/provider/deepseek.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/digitalocean.svg b/packages/ui/src/assets/icons/provider/digitalocean.svg index 5be390b9d348..695ad131686d 100644 --- a/packages/ui/src/assets/icons/provider/digitalocean.svg +++ b/packages/ui/src/assets/icons/provider/digitalocean.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/dinference.svg b/packages/ui/src/assets/icons/provider/dinference.svg index e045c96fb355..ce30e8d31ddd 100644 --- a/packages/ui/src/assets/icons/provider/dinference.svg +++ b/packages/ui/src/assets/icons/provider/dinference.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/drun.svg b/packages/ui/src/assets/icons/provider/drun.svg index 472dee9122e3..0e306be2bc74 100644 --- a/packages/ui/src/assets/icons/provider/drun.svg +++ b/packages/ui/src/assets/icons/provider/drun.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/evroc.svg b/packages/ui/src/assets/icons/provider/evroc.svg index 7597820a192d..9130a85df7b9 100644 --- a/packages/ui/src/assets/icons/provider/evroc.svg +++ b/packages/ui/src/assets/icons/provider/evroc.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/fastrouter.svg b/packages/ui/src/assets/icons/provider/fastrouter.svg index ec73dc49cdf0..6e5a45114669 100644 --- a/packages/ui/src/assets/icons/provider/fastrouter.svg +++ b/packages/ui/src/assets/icons/provider/fastrouter.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/fireworks-ai.svg b/packages/ui/src/assets/icons/provider/fireworks-ai.svg index 72cc91f094c2..bf55fdf273ed 100644 --- a/packages/ui/src/assets/icons/provider/fireworks-ai.svg +++ b/packages/ui/src/assets/icons/provider/fireworks-ai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/firmware.svg b/packages/ui/src/assets/icons/provider/firmware.svg index baa524ba2d42..22384c290de8 100644 --- a/packages/ui/src/assets/icons/provider/firmware.svg +++ b/packages/ui/src/assets/icons/provider/firmware.svg @@ -1,18 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/friendli.svg b/packages/ui/src/assets/icons/provider/friendli.svg index 8acb7632df09..1ea0ba0d4238 100644 --- a/packages/ui/src/assets/icons/provider/friendli.svg +++ b/packages/ui/src/assets/icons/provider/friendli.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/github-copilot.svg b/packages/ui/src/assets/icons/provider/github-copilot.svg index 2d426f265cf3..ea19c462c4d3 100644 --- a/packages/ui/src/assets/icons/provider/github-copilot.svg +++ b/packages/ui/src/assets/icons/provider/github-copilot.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/github-models.svg b/packages/ui/src/assets/icons/provider/github-models.svg index 39689d95c0c6..825f9c1ee53e 100644 --- a/packages/ui/src/assets/icons/provider/github-models.svg +++ b/packages/ui/src/assets/icons/provider/github-models.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/gitlab.svg b/packages/ui/src/assets/icons/provider/gitlab.svg index eef04ace2b09..cb42932781a2 100644 --- a/packages/ui/src/assets/icons/provider/gitlab.svg +++ b/packages/ui/src/assets/icons/provider/gitlab.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg b/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg +++ b/packages/ui/src/assets/icons/provider/google-vertex-anthropic.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/google-vertex.svg b/packages/ui/src/assets/icons/provider/google-vertex.svg index fda56b4479b5..165a4db84bc1 100644 --- a/packages/ui/src/assets/icons/provider/google-vertex.svg +++ b/packages/ui/src/assets/icons/provider/google-vertex.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/google.svg b/packages/ui/src/assets/icons/provider/google.svg index 4ebfcfd2b4c0..8628c8311fae 100644 --- a/packages/ui/src/assets/icons/provider/google.svg +++ b/packages/ui/src/assets/icons/provider/google.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/groq.svg b/packages/ui/src/assets/icons/provider/groq.svg index fdd22ed7dfdc..b89172200d3c 100644 --- a/packages/ui/src/assets/icons/provider/groq.svg +++ b/packages/ui/src/assets/icons/provider/groq.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/helicone.svg b/packages/ui/src/assets/icons/provider/helicone.svg index 8a4bd43ad3ef..4f4a9e44f3ae 100644 --- a/packages/ui/src/assets/icons/provider/helicone.svg +++ b/packages/ui/src/assets/icons/provider/helicone.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/huggingface.svg b/packages/ui/src/assets/icons/provider/huggingface.svg index 4241ff94e3d4..02ec3d6dce45 100644 --- a/packages/ui/src/assets/icons/provider/huggingface.svg +++ b/packages/ui/src/assets/icons/provider/huggingface.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/iflowcn.svg b/packages/ui/src/assets/icons/provider/iflowcn.svg index 6f35a7d591ae..61e21dc2185a 100644 --- a/packages/ui/src/assets/icons/provider/iflowcn.svg +++ b/packages/ui/src/assets/icons/provider/iflowcn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/inception.svg b/packages/ui/src/assets/icons/provider/inception.svg index f70ffbc785b3..005db929545a 100644 --- a/packages/ui/src/assets/icons/provider/inception.svg +++ b/packages/ui/src/assets/icons/provider/inception.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/inference.svg b/packages/ui/src/assets/icons/provider/inference.svg index c17f6657448f..b9f4f79e66db 100644 --- a/packages/ui/src/assets/icons/provider/inference.svg +++ b/packages/ui/src/assets/icons/provider/inference.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/io-net.svg b/packages/ui/src/assets/icons/provider/io-net.svg index 651e57df845a..5938ad715140 100644 --- a/packages/ui/src/assets/icons/provider/io-net.svg +++ b/packages/ui/src/assets/icons/provider/io-net.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/jiekou.svg b/packages/ui/src/assets/icons/provider/jiekou.svg index 7fe6378e561c..e70f8c415d1a 100644 --- a/packages/ui/src/assets/icons/provider/jiekou.svg +++ b/packages/ui/src/assets/icons/provider/jiekou.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/kilo.svg b/packages/ui/src/assets/icons/provider/kilo.svg index 0a761347a8e2..02933bfd33d7 100644 --- a/packages/ui/src/assets/icons/provider/kilo.svg +++ b/packages/ui/src/assets/icons/provider/kilo.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/kimi-for-coding.svg b/packages/ui/src/assets/icons/provider/kimi-for-coding.svg index 8f2af02e63e1..5955ba0fce8e 100644 --- a/packages/ui/src/assets/icons/provider/kimi-for-coding.svg +++ b/packages/ui/src/assets/icons/provider/kimi-for-coding.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg b/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg index 3d0d0c455737..ee2bd75f0f99 100644 --- a/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/llama.svg b/packages/ui/src/assets/icons/provider/llama.svg index 3053b251fcce..d46af7ceb0b4 100644 --- a/packages/ui/src/assets/icons/provider/llama.svg +++ b/packages/ui/src/assets/icons/provider/llama.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/lmstudio.svg b/packages/ui/src/assets/icons/provider/lmstudio.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/lmstudio.svg +++ b/packages/ui/src/assets/icons/provider/lmstudio.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/lucidquery.svg b/packages/ui/src/assets/icons/provider/lucidquery.svg index 6420a042eab6..c573d50dbc52 100644 --- a/packages/ui/src/assets/icons/provider/lucidquery.svg +++ b/packages/ui/src/assets/icons/provider/lucidquery.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/meganova.svg b/packages/ui/src/assets/icons/provider/meganova.svg index 59e8b3a1f271..7630d6705335 100644 --- a/packages/ui/src/assets/icons/provider/meganova.svg +++ b/packages/ui/src/assets/icons/provider/meganova.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg b/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/minimax-cn.svg b/packages/ui/src/assets/icons/provider/minimax-cn.svg index 44c5eec21d3e..ac32b6bf5430 100644 --- a/packages/ui/src/assets/icons/provider/minimax-cn.svg +++ b/packages/ui/src/assets/icons/provider/minimax-cn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg b/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/minimax.svg b/packages/ui/src/assets/icons/provider/minimax.svg index 44c5eec21d3e..ac32b6bf5430 100644 --- a/packages/ui/src/assets/icons/provider/minimax.svg +++ b/packages/ui/src/assets/icons/provider/minimax.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/mistral.svg b/packages/ui/src/assets/icons/provider/mistral.svg index 966e474bc087..188570804737 100644 --- a/packages/ui/src/assets/icons/provider/mistral.svg +++ b/packages/ui/src/assets/icons/provider/mistral.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/moark.svg b/packages/ui/src/assets/icons/provider/moark.svg index dc84a9191c78..d903d64a9c8a 100644 --- a/packages/ui/src/assets/icons/provider/moark.svg +++ b/packages/ui/src/assets/icons/provider/moark.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/modelscope.svg b/packages/ui/src/assets/icons/provider/modelscope.svg index 94a894a555b6..66af60379fd8 100644 --- a/packages/ui/src/assets/icons/provider/modelscope.svg +++ b/packages/ui/src/assets/icons/provider/modelscope.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/moonshotai-cn.svg b/packages/ui/src/assets/icons/provider/moonshotai-cn.svg index 3cdf7c868125..5c247017cdfb 100644 --- a/packages/ui/src/assets/icons/provider/moonshotai-cn.svg +++ b/packages/ui/src/assets/icons/provider/moonshotai-cn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/moonshotai.svg b/packages/ui/src/assets/icons/provider/moonshotai.svg index 3cdf7c868125..5c247017cdfb 100644 --- a/packages/ui/src/assets/icons/provider/moonshotai.svg +++ b/packages/ui/src/assets/icons/provider/moonshotai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/morph.svg b/packages/ui/src/assets/icons/provider/morph.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/morph.svg +++ b/packages/ui/src/assets/icons/provider/morph.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/nano-gpt.svg b/packages/ui/src/assets/icons/provider/nano-gpt.svg index f7fbb4cdcdef..613c58ef3fe9 100644 --- a/packages/ui/src/assets/icons/provider/nano-gpt.svg +++ b/packages/ui/src/assets/icons/provider/nano-gpt.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/nebius.svg b/packages/ui/src/assets/icons/provider/nebius.svg index aeba5890d11a..b6a322d07b4b 100644 --- a/packages/ui/src/assets/icons/provider/nebius.svg +++ b/packages/ui/src/assets/icons/provider/nebius.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/nova.svg b/packages/ui/src/assets/icons/provider/nova.svg index 9fcae228c0ae..f163427913e6 100644 --- a/packages/ui/src/assets/icons/provider/nova.svg +++ b/packages/ui/src/assets/icons/provider/nova.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/novita-ai.svg b/packages/ui/src/assets/icons/provider/novita-ai.svg index ac537b8dd424..f58e70b1f539 100644 --- a/packages/ui/src/assets/icons/provider/novita-ai.svg +++ b/packages/ui/src/assets/icons/provider/novita-ai.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/nvidia.svg b/packages/ui/src/assets/icons/provider/nvidia.svg index 1f53eefca7ce..8a2e7db454e9 100644 --- a/packages/ui/src/assets/icons/provider/nvidia.svg +++ b/packages/ui/src/assets/icons/provider/nvidia.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/ollama-cloud.svg b/packages/ui/src/assets/icons/provider/ollama-cloud.svg index 08c05cf28c50..33c5654fbe6f 100644 --- a/packages/ui/src/assets/icons/provider/ollama-cloud.svg +++ b/packages/ui/src/assets/icons/provider/ollama-cloud.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/openai.svg b/packages/ui/src/assets/icons/provider/openai.svg index 000f65c34aa5..d2e46865d7ba 100644 --- a/packages/ui/src/assets/icons/provider/openai.svg +++ b/packages/ui/src/assets/icons/provider/openai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/opencode-go.svg b/packages/ui/src/assets/icons/provider/opencode-go.svg index e0833b9230f8..3a6def94d98f 100644 --- a/packages/ui/src/assets/icons/provider/opencode-go.svg +++ b/packages/ui/src/assets/icons/provider/opencode-go.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/opencode.svg b/packages/ui/src/assets/icons/provider/opencode.svg index 95084658210b..b28984442221 100644 --- a/packages/ui/src/assets/icons/provider/opencode.svg +++ b/packages/ui/src/assets/icons/provider/opencode.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/openrouter.svg b/packages/ui/src/assets/icons/provider/openrouter.svg index 8cd31630131b..c14b662fee64 100644 --- a/packages/ui/src/assets/icons/provider/openrouter.svg +++ b/packages/ui/src/assets/icons/provider/openrouter.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/ovhcloud.svg b/packages/ui/src/assets/icons/provider/ovhcloud.svg index 064b0835be24..8162a70a0be4 100644 --- a/packages/ui/src/assets/icons/provider/ovhcloud.svg +++ b/packages/ui/src/assets/icons/provider/ovhcloud.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/perplexity-agent.svg b/packages/ui/src/assets/icons/provider/perplexity-agent.svg index a0f38862a4a0..b657b3753c66 100644 --- a/packages/ui/src/assets/icons/provider/perplexity-agent.svg +++ b/packages/ui/src/assets/icons/provider/perplexity-agent.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/perplexity.svg b/packages/ui/src/assets/icons/provider/perplexity.svg index a0f38862a4a0..b657b3753c66 100644 --- a/packages/ui/src/assets/icons/provider/perplexity.svg +++ b/packages/ui/src/assets/icons/provider/perplexity.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/poe.svg b/packages/ui/src/assets/icons/provider/poe.svg index a5ab62d725ab..306e0a6dcd7e 100644 --- a/packages/ui/src/assets/icons/provider/poe.svg +++ b/packages/ui/src/assets/icons/provider/poe.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/privatemode-ai.svg b/packages/ui/src/assets/icons/provider/privatemode-ai.svg index edb5a6d76481..6f268cd00547 100644 --- a/packages/ui/src/assets/icons/provider/privatemode-ai.svg +++ b/packages/ui/src/assets/icons/provider/privatemode-ai.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/qihang-ai.svg b/packages/ui/src/assets/icons/provider/qihang-ai.svg index 3b356637a121..fc9911abf691 100644 --- a/packages/ui/src/assets/icons/provider/qihang-ai.svg +++ b/packages/ui/src/assets/icons/provider/qihang-ai.svg @@ -1,9 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/qiniu-ai.svg b/packages/ui/src/assets/icons/provider/qiniu-ai.svg index 858560f9ffe7..2bc874f31a6a 100644 --- a/packages/ui/src/assets/icons/provider/qiniu-ai.svg +++ b/packages/ui/src/assets/icons/provider/qiniu-ai.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/requesty.svg b/packages/ui/src/assets/icons/provider/requesty.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/requesty.svg +++ b/packages/ui/src/assets/icons/provider/requesty.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/sap-ai-core.svg b/packages/ui/src/assets/icons/provider/sap-ai-core.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/sap-ai-core.svg +++ b/packages/ui/src/assets/icons/provider/sap-ai-core.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/scaleway.svg b/packages/ui/src/assets/icons/provider/scaleway.svg index 7574f72f3887..a5cf1fe05bfc 100644 --- a/packages/ui/src/assets/icons/provider/scaleway.svg +++ b/packages/ui/src/assets/icons/provider/scaleway.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/siliconflow-cn.svg b/packages/ui/src/assets/icons/provider/siliconflow-cn.svg index 13cac22b9d30..ffe32c54e379 100644 --- a/packages/ui/src/assets/icons/provider/siliconflow-cn.svg +++ b/packages/ui/src/assets/icons/provider/siliconflow-cn.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/siliconflow.svg b/packages/ui/src/assets/icons/provider/siliconflow.svg index 13cac22b9d30..ffe32c54e379 100644 --- a/packages/ui/src/assets/icons/provider/siliconflow.svg +++ b/packages/ui/src/assets/icons/provider/siliconflow.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/stackit.svg b/packages/ui/src/assets/icons/provider/stackit.svg index 0d78b781acf6..ec062eccea8d 100644 --- a/packages/ui/src/assets/icons/provider/stackit.svg +++ b/packages/ui/src/assets/icons/provider/stackit.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/stepfun.svg b/packages/ui/src/assets/icons/provider/stepfun.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/stepfun.svg +++ b/packages/ui/src/assets/icons/provider/stepfun.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/submodel.svg b/packages/ui/src/assets/icons/provider/submodel.svg index 5bef03c6492c..283348f25ab0 100644 --- a/packages/ui/src/assets/icons/provider/submodel.svg +++ b/packages/ui/src/assets/icons/provider/submodel.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/synthetic.svg b/packages/ui/src/assets/icons/provider/synthetic.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/synthetic.svg +++ b/packages/ui/src/assets/icons/provider/synthetic.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/tencent-coding-plan.svg b/packages/ui/src/assets/icons/provider/tencent-coding-plan.svg index 502e51a5be08..522db4a818c1 100644 --- a/packages/ui/src/assets/icons/provider/tencent-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/tencent-coding-plan.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/togetherai.svg b/packages/ui/src/assets/icons/provider/togetherai.svg index 68413386c0e3..579a85252a67 100644 --- a/packages/ui/src/assets/icons/provider/togetherai.svg +++ b/packages/ui/src/assets/icons/provider/togetherai.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/upstage.svg b/packages/ui/src/assets/icons/provider/upstage.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/upstage.svg +++ b/packages/ui/src/assets/icons/provider/upstage.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/v0.svg b/packages/ui/src/assets/icons/provider/v0.svg index 09f3b411e3be..303d8f1ce205 100644 --- a/packages/ui/src/assets/icons/provider/v0.svg +++ b/packages/ui/src/assets/icons/provider/v0.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/venice.svg b/packages/ui/src/assets/icons/provider/venice.svg index 3d5809e3bf19..d214582a1404 100644 --- a/packages/ui/src/assets/icons/provider/venice.svg +++ b/packages/ui/src/assets/icons/provider/venice.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/vercel.svg b/packages/ui/src/assets/icons/provider/vercel.svg index a99425f2a945..d08c1cff1b9e 100644 --- a/packages/ui/src/assets/icons/provider/vercel.svg +++ b/packages/ui/src/assets/icons/provider/vercel.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/vivgrid.svg b/packages/ui/src/assets/icons/provider/vivgrid.svg index 928fa3ff1ed2..37c27d0d08f0 100644 --- a/packages/ui/src/assets/icons/provider/vivgrid.svg +++ b/packages/ui/src/assets/icons/provider/vivgrid.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/vultr.svg b/packages/ui/src/assets/icons/provider/vultr.svg index 9205d4484555..3b5e25a73de3 100644 --- a/packages/ui/src/assets/icons/provider/vultr.svg +++ b/packages/ui/src/assets/icons/provider/vultr.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/wandb.svg b/packages/ui/src/assets/icons/provider/wandb.svg index 086e9aa1fca1..ce1f77422bf4 100644 --- a/packages/ui/src/assets/icons/provider/wandb.svg +++ b/packages/ui/src/assets/icons/provider/wandb.svg @@ -1,24 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/xai.svg b/packages/ui/src/assets/icons/provider/xai.svg index ccd22443c491..db93091d7d7c 100644 --- a/packages/ui/src/assets/icons/provider/xai.svg +++ b/packages/ui/src/assets/icons/provider/xai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/xiaomi.svg b/packages/ui/src/assets/icons/provider/xiaomi.svg index 4a893919e0d7..a8f35bb2739a 100644 --- a/packages/ui/src/assets/icons/provider/xiaomi.svg +++ b/packages/ui/src/assets/icons/provider/xiaomi.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/zai-coding-plan.svg b/packages/ui/src/assets/icons/provider/zai-coding-plan.svg index d7da9b7c5f35..5fd25266ea0c 100644 --- a/packages/ui/src/assets/icons/provider/zai-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/zai-coding-plan.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/zai.svg b/packages/ui/src/assets/icons/provider/zai.svg index d7da9b7c5f35..5fd25266ea0c 100644 --- a/packages/ui/src/assets/icons/provider/zai.svg +++ b/packages/ui/src/assets/icons/provider/zai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/zenmux.svg b/packages/ui/src/assets/icons/provider/zenmux.svg index 9eb8045e453a..77203134ef76 100644 --- a/packages/ui/src/assets/icons/provider/zenmux.svg +++ b/packages/ui/src/assets/icons/provider/zenmux.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/zhipuai-coding-plan.svg b/packages/ui/src/assets/icons/provider/zhipuai-coding-plan.svg index 3d0d0c455737..ee2bd75f0f99 100644 --- a/packages/ui/src/assets/icons/provider/zhipuai-coding-plan.svg +++ b/packages/ui/src/assets/icons/provider/zhipuai-coding-plan.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/zhipuai.svg b/packages/ui/src/assets/icons/provider/zhipuai.svg index d7da9b7c5f35..5fd25266ea0c 100644 --- a/packages/ui/src/assets/icons/provider/zhipuai.svg +++ b/packages/ui/src/assets/icons/provider/zhipuai.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/packages/ui/src/components/app-icons/sprite.svg b/packages/ui/src/components/app-icons/sprite.svg index 68361f4137ba..ffbef6bd56dc 100644 --- a/packages/ui/src/components/app-icons/sprite.svg +++ b/packages/ui/src/components/app-icons/sprite.svg @@ -1,114 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/components/card.css b/packages/ui/src/components/card.css deleted file mode 100644 index 801b2a589494..000000000000 --- a/packages/ui/src/components/card.css +++ /dev/null @@ -1,94 +0,0 @@ -[data-component="card"] { - --card-pad-y: 10px; - --card-pad-r: 12px; - --card-pad-l: 10px; - - width: 100%; - display: flex; - flex-direction: column; - position: relative; - background: transparent; - border: none; - border-radius: var(--radius-md); - padding: var(--card-pad-y) var(--card-pad-r) var(--card-pad-y) var(--card-pad-l); - - /* text-14-regular */ - font-family: var(--font-family-sans); - font-size: var(--font-size-base); - font-style: normal; - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); - letter-spacing: var(--letter-spacing-normal); - color: var(--v2-text-text-base); - - --card-gap: 8px; - --card-icon: 16px; - --card-indent: 0px; - --card-line-pad: 8px; - - --card-accent: var(--v2-icon-icon-base); - - &:has([data-slot="card-title"]) { - gap: 8px; - } - - &:has([data-slot="card-title-icon"]) { - --card-indent: calc(var(--card-icon) + var(--card-gap)); - } - - &::before { - content: ""; - position: absolute; - left: 0; - top: var(--card-line-pad); - bottom: var(--card-line-pad); - width: 2px; - border-radius: 2px; - background-color: var(--card-accent); - } - - :where([data-card="title"], [data-slot="card-title"]) { - color: var(--v2-text-text-base); - font-weight: var(--font-weight-medium); - } - - :where([data-slot="card-title"]) { - display: flex; - align-items: center; - gap: var(--card-gap); - } - - :where([data-slot="card-title"]) [data-slot="icon-svg"] { - color: var(--card-accent); - } - - :where([data-slot="card-title-icon"]) { - display: inline-flex; - align-items: center; - justify-content: center; - width: var(--card-icon); - height: var(--card-icon); - flex: 0 0 auto; - } - - :where([data-slot="card-title-icon"][data-placeholder]) [data-slot="icon-svg"] { - color: var(--v2-text-text-faint); - } - - :where([data-slot="card-title-icon"]) - [data-slot="icon-svg"] - :is(path, line, polyline, polygon, rect, circle, ellipse)[stroke] { - stroke-width: 1.5px !important; - } - - :where([data-card="description"], [data-slot="card-description"]) { - color: var(--v2-text-text-muted); - white-space: pre-wrap; - overflow-wrap: anywhere; - word-break: break-word; - } - - :where([data-card="actions"], [data-slot="card-actions"]) { - padding-left: var(--card-indent); - } -} diff --git a/packages/ui/src/components/dock-surface.css b/packages/ui/src/components/dock-surface.css index 5d0b2db1b685..310f8dd0990b 100644 --- a/packages/ui/src/components/dock-surface.css +++ b/packages/ui/src/components/dock-surface.css @@ -1,10 +1,20 @@ [data-dock-surface="shell"] { background-color: var(--surface-raised-stronger-non-alpha); - box-shadow: var(--shadow-xs-border); + box-shadow: + var(--shadow-xs-border), + 0 -4px 16px -4px hsl(0 0% 0% / 0.06); position: relative; z-index: 10; - border-radius: 12px; + border-radius: 14px; overflow: clip; + transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +[data-dock-surface="shell"]:focus-within { + box-shadow: + var(--shadow-xs-border), + 0 -4px 20px -4px hsl(0 0% 0% / 0.08), + 0 0 0 1px var(--border-interactive-base); } [data-dock-border-underlay] { @@ -19,8 +29,9 @@ border: 1px solid var(--border-weak-base); position: relative; z-index: 0; - border-radius: 12px; + border-radius: 14px; overflow: clip; + transition: border-color 200ms cubic-bezier(0.4, 0, 0.2, 1); } [data-dock-surface="tray"][data-dock-attach="top"] { diff --git a/packages/ui/src/components/file-icons/sprite.svg b/packages/ui/src/components/file-icons/sprite.svg index 619b2b58cace..622bea89a301 100644 --- a/packages/ui/src/components/file-icons/sprite.svg +++ b/packages/ui/src/components/file-icons/sprite.svg @@ -1,11707 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css index fe9b404d6a9f..8e9e6d630bf5 100644 --- a/packages/ui/src/components/list.css +++ b/packages/ui/src/components/list.css @@ -237,6 +237,9 @@ align-items: center; color: var(--text-strong); scroll-margin-top: 28px; + transition: + background-color 100ms cubic-bezier(0.4, 0, 0.2, 1), + border-radius 100ms cubic-bezier(0.4, 0, 0.2, 1); /* text-14-medium */ font-family: var(--font-family-sans); diff --git a/packages/ui/src/components/popover.css b/packages/ui/src/components/popover.css index b49542afd9b8..c09158acf559 100644 --- a/packages/ui/src/components/popover.css +++ b/packages/ui/src/components/popover.css @@ -6,12 +6,12 @@ z-index: 50; min-width: 200px; max-width: 320px; - border-radius: var(--radius-md); + border-radius: var(--radius-lg); background-color: var(--surface-raised-stronger-non-alpha); border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent); background-clip: padding-box; - box-shadow: var(--shadow-md); + box-shadow: var(--shadow-lg); transform-origin: var(--kb-popover-content-transform-origin); @@ -78,21 +78,21 @@ @keyframes popover-open { from { opacity: 0; - transform: scale(0.96); + transform: scale(0.95) translateY(2px); } to { opacity: 1; - transform: scale(1); + transform: scale(1) translateY(0); } } @keyframes popover-close { from { opacity: 1; - transform: scale(1); + transform: scale(1) translateY(0); } to { opacity: 0; - transform: scale(0.96); + transform: scale(0.95) translateY(2px); } } diff --git a/packages/ui/src/components/provider-icons/sprite.svg b/packages/ui/src/components/provider-icons/sprite.svg index 259f696182c0..095bc6968ffa 100644 --- a/packages/ui/src/components/provider-icons/sprite.svg +++ b/packages/ui/src/components/provider-icons/sprite.svg @@ -1,1108 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/ui/src/styles/animations.css b/packages/ui/src/styles/animations.css index f9a09df379e1..09146cb79972 100644 --- a/packages/ui/src/styles/animations.css +++ b/packages/ui/src/styles/animations.css @@ -1,8 +1,30 @@ +/* ============================================ + OpenCode Animation System + Refined micro-interactions & motion design + ============================================ */ + :root { + /* Animation tokens */ + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); + --ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1); + + /* Duration tokens */ + --duration-instant: 75ms; + --duration-fast: 120ms; + --duration-normal: 200ms; + --duration-slow: 300ms; + --duration-slower: 450ms; + + /* Existing pulse animations */ --animate-pulse: pulse-opacity 2s ease-in-out infinite; --animate-pulse-scale: pulse-scale 1.2s ease-in-out infinite; } +/* ---- Pulse animations ---- */ + @keyframes pulse-opacity { 0%, 100% { @@ -33,6 +55,8 @@ } } +/* ---- Entrance animations ---- */ + @keyframes fadeUp { from { opacity: 0; @@ -44,98 +68,145 @@ } } -.fade-up-text { - animation: fadeUp 0.4s ease-out forwards; - opacity: 0; - - &:nth-child(1) { - animation-delay: 0.1s; - } - &:nth-child(2) { - animation-delay: 0.2s; - } - &:nth-child(3) { - animation-delay: 0.3s; - } - &:nth-child(4) { - animation-delay: 0.4s; - } - &:nth-child(5) { - animation-delay: 0.5s; - } - &:nth-child(6) { - animation-delay: 0.6s; - } - &:nth-child(7) { - animation-delay: 0.7s; - } - &:nth-child(8) { - animation-delay: 0.8s; - } - &:nth-child(9) { - animation-delay: 0.9s; - } - &:nth-child(10) { - animation-delay: 1s; - } - &:nth-child(11) { - animation-delay: 1.1s; - } - &:nth-child(12) { - animation-delay: 1.2s; - } - &:nth-child(13) { - animation-delay: 1.3s; +@keyframes fadeIn { + from { + opacity: 0; } - &:nth-child(14) { - animation-delay: 1.4s; + to { + opacity: 1; } - &:nth-child(15) { - animation-delay: 1.5s; +} + +@keyframes fadeInScale { + from { + opacity: 0; + transform: scale(0.95); } - &:nth-child(16) { - animation-delay: 1.6s; + to { + opacity: 1; + transform: scale(1); } - &:nth-child(17) { - animation-delay: 1.7s; +} + +@keyframes slideInFromRight { + from { + opacity: 0; + transform: translateX(8px); } - &:nth-child(18) { - animation-delay: 1.8s; + to { + opacity: 1; + transform: translateX(0); } - &:nth-child(19) { - animation-delay: 1.9s; +} + +@keyframes slideInFromLeft { + from { + opacity: 0; + transform: translateX(-8px); } - &:nth-child(20) { - animation-delay: 2s; + to { + opacity: 1; + transform: translateX(0); } - &:nth-child(21) { - animation-delay: 2.1s; +} + +@keyframes slideInFromBottom { + from { + opacity: 0; + transform: translateY(12px); } - &:nth-child(22) { - animation-delay: 2.2s; + to { + opacity: 1; + transform: translateY(0); } - &:nth-child(23) { - animation-delay: 2.3s; +} + +/* ---- Subtle glow for active/focus states ---- */ + +@keyframes subtleGlow { + 0%, + 100% { + box-shadow: 0 0 0 0 transparent; } - &:nth-child(24) { - animation-delay: 2.4s; + 50% { + box-shadow: 0 0 8px 2px color-mix(in srgb, var(--border-selected) 25%, transparent); } - &:nth-child(25) { - animation-delay: 2.5s; +} + +/* ---- Shimmer for loading states ---- */ + +@keyframes shimmer { + 0% { + background-position: -200% 0; } - &:nth-child(26) { - animation-delay: 2.6s; + 100% { + background-position: 200% 0; } - &:nth-child(27) { - animation-delay: 2.7s; +} + +/* ---- Spin ---- */ + +@keyframes spin { + from { + transform: rotate(0deg); } - &:nth-child(28) { - animation-delay: 2.8s; + to { + transform: rotate(360deg); } - &:nth-child(29) { - animation-delay: 2.9s; +} + +/* ---- Staggered fade-up text ---- */ + +.fade-up-text { + animation: fadeUp 0.4s var(--ease-out-expo) forwards; + opacity: 0; + + &:nth-child(1) { animation-delay: 0.05s; } + &:nth-child(2) { animation-delay: 0.1s; } + &:nth-child(3) { animation-delay: 0.15s; } + &:nth-child(4) { animation-delay: 0.2s; } + &:nth-child(5) { animation-delay: 0.25s; } + &:nth-child(6) { animation-delay: 0.3s; } + &:nth-child(7) { animation-delay: 0.35s; } + &:nth-child(8) { animation-delay: 0.4s; } + &:nth-child(9) { animation-delay: 0.45s; } + &:nth-child(10) { animation-delay: 0.5s; } + &:nth-child(11) { animation-delay: 0.55s; } + &:nth-child(12) { animation-delay: 0.6s; } + &:nth-child(13) { animation-delay: 0.65s; } + &:nth-child(14) { animation-delay: 0.7s; } + &:nth-child(15) { animation-delay: 0.75s; } + &:nth-child(16) { animation-delay: 0.8s; } + &:nth-child(17) { animation-delay: 0.85s; } + &:nth-child(18) { animation-delay: 0.9s; } + &:nth-child(19) { animation-delay: 0.95s; } + &:nth-child(20) { animation-delay: 1s; } + &:nth-child(21) { animation-delay: 1.05s; } + &:nth-child(22) { animation-delay: 1.1s; } + &:nth-child(23) { animation-delay: 1.15s; } + &:nth-child(24) { animation-delay: 1.2s; } + &:nth-child(25) { animation-delay: 1.25s; } + &:nth-child(26) { animation-delay: 1.3s; } + &:nth-child(27) { animation-delay: 1.35s; } + &:nth-child(28) { animation-delay: 1.4s; } + &:nth-child(29) { animation-delay: 1.45s; } + &:nth-child(30) { animation-delay: 1.5s; } +} + +/* ---- Reduced motion preference ---- */ + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; } - &:nth-child(30) { - animation-delay: 3s; + + .fade-up-text { + opacity: 1; + animation: none; } } diff --git a/packages/ui/src/styles/base.css b/packages/ui/src/styles/base.css deleted file mode 100644 index ebcc8ad6ccf2..000000000000 --- a/packages/ui/src/styles/base.css +++ /dev/null @@ -1,423 +0,0 @@ -/* - 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) - 2. Remove default margins and padding - 3. Reset all borders. -*/ - -*, -::after, -::before, -::backdrop, -::file-selector-button { - box-sizing: border-box; /* 1 */ - margin: 0; /* 2 */ - padding: 0; /* 2 */ - border: 0 solid; /* 3 */ -} - -/* - 1. Use a consistent sensible line-height in all browsers. - 2. Prevent adjustments of font size after orientation changes in iOS. - 3. Use a more readable tab size. - 4. Use the user's configured `sans` font-family by default. - 5. Use the user's configured `sans` font-feature-settings by default. - 6. Use the user's configured `sans` font-variation-settings by default. - 7. Disable tap highlights on iOS. - 8. Avoid one-word last lines in all wrapped text. Chromium keeps the line count, so heights do not change. -*/ - -html, -:host { - line-height: var(--line-height-large); /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ - tab-size: 4; /* 3 */ - font-family: var(--font-family-sans); /* 4 */ - font-feature-settings: var(--font-family-sans--font-feature-settings, normal); /* 5 */ - font-variation-settings: var(--font-family-sans--font-variation-settings, normal); /* 6 */ - -webkit-tap-highlight-color: transparent; /* 7 */ - text-wrap-style: pretty; /* 8 */ -} - -/* - 1. Add the correct height in Firefox. - 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) - 3. Reset the default border style to a 1px solid border. -*/ - -hr { - height: 0; /* 1 */ - color: inherit; /* 2 */ - border-top-width: 1px; /* 3 */ -} - -/* - Add the correct text decoration in Chrome, Edge, and Safari. -*/ - -abbr:where([title]) { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; -} - -/* - Remove the default font size and weight for headings. -*/ - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; -} - -/* - Reset links to optimize for opt-in styling instead of opt-out. -*/ - -a { - color: inherit; - -webkit-text-decoration: inherit; - text-decoration: inherit; - cursor: default; -} - -#root:not([aria-hidden]) *[data-tauri-drag-region] { - app-region: drag; -} - -*[data-tauri-drag-region] button, -*[data-tauri-drag-region] a, -*[data-tauri-drag-region] input, -*[data-tauri-drag-region] textarea, -*[data-tauri-drag-region] select, -*[data-tauri-drag-region] [role="button"], -*[data-tauri-drag-region] [role="menuitem"], -*[data-tauri-drag-region] [contenteditable] { - app-region: no-drag; -} - -/* - Add the correct font weight in Edge and Safari. -*/ - -b, -strong { - font-weight: bolder; -} - -/* - 1. Use the user's configured `mono` font-family by default. - 2. Use the user's configured `mono` font-feature-settings by default. - 3. Use the user's configured `mono` font-variation-settings by default. - 4. Correct the odd `em` font sizing in all browsers. - 5. Keep code wrapping greedy so wrapped lines break where an editor would. -*/ - -code, -kbd, -samp, -pre { - font-family: var(--font-family-mono); /* 1 */ - font-feature-settings: var(--font-family-mono--font-feature-settings, normal); /* 2 */ - font-variation-settings: var(--font-family-mono--font-variation-settings, normal); /* 3 */ - font-size: 1em; /* 4 */ - text-wrap-style: auto; /* 5 */ -} - -/* - Keep earlier lines still while the user types. -*/ - -input, -textarea, -[contenteditable] { - text-wrap-style: stable; -} - -/* - Add the correct font size in all browsers. -*/ - -small { - font-size: 80%; -} - -/* - Prevent `sub` and `sup` elements from affecting the line height in all browsers. -*/ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* - 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) - 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) - 3. Remove gaps between table borders by default. -*/ - -table { - text-indent: 0; /* 1 */ - border-color: inherit; /* 2 */ - border-collapse: collapse; /* 3 */ -} - -/* - Use the modern Firefox focus style for all focusable elements. -*/ - -:-moz-focusring { - outline: auto; -} - -/* - Add the correct vertical alignment in Chrome and Firefox. -*/ - -progress { - vertical-align: baseline; -} - -/* - Add the correct display in Chrome and Safari. -*/ - -summary { - display: list-item; -} - -/* - Make lists unstyled by default. -*/ - -ol, -ul, -menu { - list-style: none; -} - -/* - 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) - 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) - This can trigger a poorly considered lint error in some tools but is included by design. -*/ - -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; /* 1 */ - vertical-align: middle; /* 2 */ -} - -/* - Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) -*/ - -img, -video { - max-width: 100%; - height: auto; -} - -/* - 1. Inherit font styles in all browsers. - 2. Remove border radius in all browsers. - 3. Remove background color in all browsers. - 4. Ensure consistent opacity for disabled states in all browsers. -*/ - -button, -input, -select, -optgroup, -textarea, -::file-selector-button { - font: inherit; /* 1 */ - font-feature-settings: inherit; /* 1 */ - font-variation-settings: inherit; /* 1 */ - letter-spacing: inherit; /* 1 */ - color: inherit; /* 1 */ - border-radius: 0; /* 2 */ - background-color: transparent; /* 3 */ - opacity: 1; /* 4 */ -} - -/* - Restore default font weight. -*/ - -:where(select:is([multiple], [size])) optgroup { - font-weight: bolder; -} - -/* - Restore indentation. -*/ - -:where(select:is([multiple], [size])) optgroup option { - padding-inline-start: 20px; -} - -/* - Restore space after button. -*/ - -::file-selector-button { - margin-inline-end: 4px; -} - -/* - Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) -*/ - -::placeholder { - opacity: 1; -} - -/* - Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not - crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194) -*/ - -@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or (contain-intrinsic-size: 1px) - /* Safari 17+ */ { - ::placeholder { - color: color-mix(in oklab, currentcolor 50%, transparent); - } -} - -/* - Prevent resizing textareas horizontally by default. -*/ - -textarea { - resize: vertical; -} - -/* - Remove the inner padding in Chrome and Safari on macOS. -*/ - -::-webkit-search-decoration { - -webkit-appearance: none; -} - -/* - 1. Ensure date/time inputs have the same height when empty in iOS Safari. - 2. Ensure text alignment can be changed on date/time inputs in iOS Safari. -*/ - -::-webkit-date-and-time-value { - min-height: 1lh; /* 1 */ - text-align: inherit; /* 2 */ -} - -/* - Prevent height from changing on date/time inputs in macOS Safari when the input is set to `display: block`. -*/ - -::-webkit-datetime-edit { - display: inline-flex; -} - -/* - Remove excess padding from pseudo-elements in date/time inputs to ensure consistent height across browsers. -*/ - -::-webkit-datetime-edit-fields-wrapper { - padding: 0; -} - -::-webkit-datetime-edit, -::-webkit-datetime-edit-year-field, -::-webkit-datetime-edit-month-field, -::-webkit-datetime-edit-day-field, -::-webkit-datetime-edit-hour-field, -::-webkit-datetime-edit-minute-field, -::-webkit-datetime-edit-second-field, -::-webkit-datetime-edit-millisecond-field, -::-webkit-datetime-edit-meridiem-field { - padding-block: 0; -} - -/* - Center dropdown marker shown on inputs with paired ``s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499) -*/ - -::-webkit-calendar-picker-indicator { - line-height: 1; -} - -/* - Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) -*/ - -:-moz-ui-invalid { - box-shadow: none; -} - -/* - Correct the inability to style the border radius in iOS Safari. -*/ - -button, -input:where([type="button"], [type="reset"], [type="submit"]), -::file-selector-button { - appearance: button; -} - -/* - Correct the cursor style of increment and decrement buttons in Safari. -*/ - -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { - height: auto; -} - -/* - Make elements with the HTML hidden attribute stay hidden by default. -*/ - -[hidden]:where(:not([hidden="until-found"])) { - display: none !important; -} - -/* - Prevent iOS Safari from auto-zooming on input focus. - iOS WebKit zooms on any input with font-size < 16px as an accessibility feature. -*/ - -@media (hover: none) and (pointer: coarse) { - input, - select, - textarea, - [contenteditable="true"] { - font-size: 16px !important; - } - - input::placeholder, - textarea::placeholder { - font-size: 13px; - } -} diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css deleted file mode 100644 index 6d0cccdfb99d..000000000000 --- a/packages/ui/src/styles/theme.css +++ /dev/null @@ -1,632 +0,0 @@ -:root { - --font-family-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - --font-family-sans--font-feature-settings: normal; - --font-family-mono: - "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --font-family-mono--font-feature-settings: normal; - - --font-size-small: 13px; - --font-size-base: 14px; - --font-size-large: 16px; - --font-size-x-large: 20px; - --font-weight-regular: 400; - --font-weight-medium: 500; - --font-weight-bold: 670; - --line-height-normal: 130%; - --line-height-large: 150%; - --line-height-x-large: 180%; - --line-height-2x-large: 200%; - --letter-spacing-normal: 0; - --letter-spacing-tight: -0.1599999964237213; - --letter-spacing-tightest: -0.3199999928474426; - --paragraph-spacing-base: 0; - - --spacing: 0.25rem; - - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; - --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - - --radius-xs: 0.125rem; - --radius-sm: 0.25rem; - --radius-md: 0.375rem; - --radius-lg: 0.5rem; - --radius-xl: 0.625rem; - - --shadow-xs: - 0 1px 2px -0.5px light-dark(hsl(0 0% 0% / 0.04), hsl(0 0% 0% / 0.06)), - 0 0.5px 1.5px 0 light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.08)), - 0 1px 3px 0 light-dark(hsl(0 0% 0% / 0.05), hsl(0 0% 0% / 0.1)); - --shadow-md: - 0 6px 12px -2px light-dark(hsl(0 0% 0% / 0.075), hsl(0 0% 0% / 0.1)), - 0 4px 8px -2px light-dark(hsl(0 0% 0% / 0.075), hsl(0 0% 0% / 0.15)), - 0 1px 2px light-dark(hsl(0 0% 0% / 0.1), hsl(0 0% 0% / 0.15)); - --shadow-lg: - 0 16px 48px -6px light-dark(hsl(0 0% 0% / 0.05), hsl(0 0% 0% / 0.15)), - 0 6px 12px -2px light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.1)), - 0 1px 2.5px light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.1)); - --shadow-xxs-border: 0 0 0 0.5px var(--border-weak-base, rgba(0, 0, 0, 0.07)); - --shadow-xs-border: - 0 0 0 1px var(--border-base, rgba(11, 6, 0, 0.2)), 0 1px 2px -1px rgba(19, 16, 16, 0.04), - 0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08); - --shadow-xs-border-base: - 0 0 0 1px var(--border-weak-base, rgba(17, 0, 0, 0.12)), 0 1px 2px -1px rgba(19, 16, 16, 0.04), - 0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08); - --shadow-xs-border-select: - 0 0 0 3px var(--border-weak-selected, rgba(1, 103, 255, 0.29)), - 0 0 0 1px var(--border-selected, rgba(0, 74, 255, 0.99)), 0 1px 2px -1px rgba(19, 16, 16, 0.25), - 0 1px 2px 0 rgba(19, 16, 16, 0.08), 0 1px 3px 0 rgba(19, 16, 16, 0.12); - --shadow-xs-border-focus: - 0 0 0 1px var(--border-base, rgba(11, 6, 0, 0.2)), 0 1px 2px -1px rgba(19, 16, 16, 0.25), - 0 1px 2px 0 rgba(19, 16, 16, 0.08), 0 1px 3px 0 rgba(19, 16, 16, 0.12), 0 0 0 2px var(--background-weak, #f1f0f0), - 0 0 0 3px var(--border-selected, rgba(0, 74, 255, 0.99)); - --shadow-xs-border-hover: - 0 0 0 1px var(--border-weak-selected, rgba(0, 112, 255, 0.22)), 0 1px 2px -1px rgba(19, 16, 16, 0.04), - 0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08); - --shadow-xs-border-critical-base: 0 0 0 1px var(--border-critical-selected, #fc543a); - --shadow-xs-border-critical-focus: - 0 0 0 3px var(--border-critical-weak, rgba(251, 34, 0, 0.18)), 0 0 0 1px var(--border-critical-selected, #fc543a), - 0 1px 2px -1px rgba(19, 16, 16, 0.25), 0 1px 2px 0 rgba(19, 16, 16, 0.08), 0 1px 3px 0 rgba(19, 16, 16, 0.12); - --shadow-lg-border-base: - 0 0 0 1px var(--border-weak-base, rgba(0, 0, 0, 0.07)), 0 36px 80px 0 rgba(0, 0, 0, 0.03), - 0 13.141px 29.201px 0 rgba(0, 0, 0, 0.04), 0 6.38px 14.177px 0 rgba(0, 0, 0, 0.05), - 0 3.127px 6.95px 0 rgba(0, 0, 0, 0.06), 0 1.237px 2.748px 0 rgba(0, 0, 0, 0.09); - - color-scheme: light; - --text-mix-blend-mode: multiply; - - /* OC-2 fallback variables (light) */ - --background-base: #f8f8f8; - --background-weak: #f3f3f3; - --background-strong: #fcfcfc; - --background-stronger: #fcfcfc; - --surface-base: rgba(0, 0, 0, 0.031); - --base: rgba(0, 0, 0, 0.034); - --surface-base-hover: rgba(0, 0, 0, 0.059); - --surface-base-active: rgba(0, 0, 0, 0.051); - --surface-base-interactive-active: rgba(3, 76, 255, 0.09); - --base2: rgba(0, 0, 0, 0.034); - --base3: rgba(0, 0, 0, 0.034); - --surface-inset-base: rgba(0, 0, 0, 0.034); - --surface-inset-base-hover: rgba(0, 0, 0, 0.055); - --surface-inset-strong: rgba(0, 0, 0, 0.09); - --surface-inset-strong-hover: rgba(0, 0, 0, 0.09); - --surface-raised-base: rgba(0, 0, 0, 0.031); - --surface-float-base: #161616; - --surface-float-base-hover: #1c1c1c; - --surface-raised-base-hover: rgba(0, 0, 0, 0.051); - --surface-raised-base-active: rgba(0, 0, 0, 0.09); - --surface-raised-strong: #fcfcfc; - --surface-raised-strong-hover: #ffffff; - --surface-raised-stronger: #ffffff; - --surface-raised-stronger-hover: #ffffff; - --surface-weak: rgba(0, 0, 0, 0.051); - --surface-weaker: rgba(0, 0, 0, 0.071); - --surface-strong: #ffffff; - --surface-stronger-non-alpha: var(--surface-raised-stronger-non-alpha); - --surface-raised-stronger-non-alpha: #ffffff; - --surface-brand-base: #dcde8d; - --surface-brand-hover: #d0d283; - --surface-interactive-base: #ecf3ff; - --surface-interactive-hover: #e0eaff; - --surface-interactive-weak: #f7faff; - --surface-interactive-weak-hover: #ecf3ff; - --surface-success-base: #dbfed7; - --surface-success-weak: #f0feee; - --surface-success-strong: #12c905; - --surface-warning-base: #fcf3cb; - --surface-warning-weak: #fdfaec; - --surface-warning-strong: #fbdd46; - --surface-critical-base: #fff2f0; - --surface-critical-weak: #fff8f6; - --surface-critical-strong: #fc533a; - --surface-info-base: #fdecfe; - --surface-info-weak: #fef7ff; - --surface-info-strong: #a753ae; - --surface-diff-unchanged-base: #ffffff00; - --surface-diff-skip-base: #f8f8f8; - --surface-diff-hidden-base: #eaf4ff; - --surface-diff-hidden-weak: #f6faff; - --surface-diff-hidden-weaker: #fbfdff; - --surface-diff-hidden-strong: #cae3ff; - --surface-diff-hidden-stronger: #2090f5; - --surface-diff-add-base: #e3fae1; - --surface-diff-add-weak: #f4fcf3; - --surface-diff-add-weaker: #fbfefb; - --surface-diff-add-strong: #c2eebf; - --surface-diff-add-stronger: #9ff29a; - --surface-diff-delete-base: #feefeb; - --surface-diff-delete-weak: #fff8f6; - --surface-diff-delete-weaker: #fffcfb; - --surface-diff-delete-strong: #fdc3b7; - --surface-diff-delete-stronger: #fc533a; - --input-base: #fcfcfc; - --input-hover: #f8f8f8; - --input-active: #fcfdff; - --input-selected: #e0eaff; - --input-focus: #fcfdff; - --input-disabled: #ededed; - --text-base: #6f6f6f; - --text-weak: #8f8f8f; - --text-weaker: #c7c7c7; - --text-strong: #171717; - --text-invert-base: #f8f8f8; - --text-invert-weak: #f3f3f3; - --text-invert-weaker: #ededed; - --text-invert-strong: #fcfcfc; - --text-interactive-base: #034cff; - --text-on-brand-base: rgba(0, 0, 0, 0.574); - --text-on-interactive-base: #fcfcfc; - --text-on-interactive-weak: rgba(0, 0, 0, 0.574); - --text-on-success-base: #2dba26; - --text-on-critical-base: #ed4831; - --text-on-critical-weak: #fe806a; - --text-on-critical-strong: #601a0f; - --text-on-warning-base: rgba(0, 0, 0, 0.574); - --text-on-info-base: rgba(0, 0, 0, 0.574); - --text-diff-add-base: #3a8437; - --text-diff-delete-base: #ed4831; - --text-diff-delete-strong: #601a0f; - --text-diff-add-strong: #1d3e1c; - --text-on-info-weak: rgba(0, 0, 0, 0.453); - --text-on-info-strong: rgba(0, 0, 0, 0.915); - --text-on-warning-weak: rgba(0, 0, 0, 0.453); - --text-on-warning-strong: rgba(0, 0, 0, 0.915); - --text-on-success-weak: #96ec8e; - --text-on-success-strong: #044202; - --text-on-brand-weak: rgba(0, 0, 0, 0.453); - --text-on-brand-weaker: rgba(0, 0, 0, 0.232); - --text-on-brand-strong: rgba(0, 0, 0, 0.915); - --button-primary-base: #171717; - --button-secondary-base: #fcfcfc; - --button-secondary-hover: #f8f8f8; - --button-ghost-hover: rgba(0, 0, 0, 0.031); - --button-ghost-hover2: rgba(0, 0, 0, 0.051); - --border-base: rgba(0, 0, 0, 0.162); - --border-hover: rgba(0, 0, 0, 0.236); - --border-active: rgba(0, 0, 0, 0.46); - --border-selected: rgba(3, 76, 255, 0.99); - --border-disabled: rgba(0, 0, 0, 0.236); - --border-focus: rgba(0, 0, 0, 0.46); - --border-weak-base: #e5e5e5; - --border-strong-base: rgba(0, 0, 0, 0.151); - --border-strong-hover: rgba(0, 0, 0, 0.232); - --border-strong-active: rgba(0, 0, 0, 0.151); - --border-strong-selected: rgba(3, 76, 255, 0.31); - --border-strong-disabled: rgba(0, 0, 0, 0.118); - --border-strong-focus: rgba(0, 0, 0, 0.151); - --border-weak-hover: rgba(0, 0, 0, 0.118); - --border-weak-active: rgba(0, 0, 0, 0.151); - --border-weak-selected: rgba(3, 76, 255, 0.24); - --border-weak-disabled: rgba(0, 0, 0, 0.118); - --border-weak-focus: rgba(0, 0, 0, 0.151); - --border-weaker-base: #f0f0f0; - --border-weaker-hover: rgba(0, 0, 0, 0.075); - --border-weaker-active: rgba(0, 0, 0, 0.118); - --border-weaker-selected: rgba(3, 76, 255, 0.16); - --border-weaker-disabled: rgba(0, 0, 0, 0.034); - --border-weaker-focus: rgba(0, 0, 0, 0.118); - --border-interactive-base: #a3c1fd; - --border-interactive-hover: #7ea9ff; - --border-interactive-active: #034cff; - --border-interactive-selected: #034cff; - --border-interactive-disabled: #c7c7c7; - --border-interactive-focus: #034cff; - --border-success-base: #96ec8e; - --border-success-hover: #7add71; - --border-success-selected: #12c905; - --border-warning-base: #e8d479; - --border-warning-hover: #d8c158; - --border-warning-selected: #fbdd46; - --border-critical-base: #fdc3b7; - --border-critical-hover: #ffa796; - --border-critical-selected: #fc533a; - --border-info-base: #f4bdf8; - --border-info-hover: #e6a8ea; - --border-info-selected: #a753ae; - --border-color: #ffffff; - --icon-base: #8f8f8f; - --icon-hover: #6f6f6f; - --icon-active: #171717; - --icon-selected: #171717; - --icon-disabled: #c7c7c7; - --icon-focus: #171717; - --icon-invert-base: #ffffff; - --icon-weak-base: #dbdbdb; - --icon-weak-hover: #c7c7c7; - --icon-weak-active: #8f8f8f; - --icon-weak-selected: #858585; - --icon-weak-disabled: #e2e2e2; - --icon-weak-focus: #8f8f8f; - --icon-strong-base: #171717; - --icon-strong-hover: #151313; - --icon-strong-active: #020202; - --icon-strong-selected: #020202; - --icon-strong-disabled: #c7c7c7; - --icon-strong-focus: #020202; - --icon-brand-base: #171717; - --icon-interactive-base: #034cff; - --icon-success-base: #7add71; - --icon-success-hover: #4cc944; - --icon-success-active: #078901; - --icon-warning-base: #ebb76e; - --icon-warning-hover: #da9e40; - --icon-warning-active: #95671b; - --icon-critical-base: #ed4831; - --icon-critical-hover: #ca2d17; - --icon-critical-active: #601a0f; - --icon-info-base: #e6a8ea; - --icon-info-hover: #d58cda; - --icon-info-active: #9b4da1; - --icon-on-brand-base: rgba(0, 0, 0, 0.574); - --icon-on-brand-hover: rgba(0, 0, 0, 0.915); - --icon-on-brand-selected: rgba(0, 0, 0, 0.915); - --icon-on-interactive-base: #fcfcfc; - --icon-agent-plan-base: #a753ae; - --icon-agent-docs-base: #fcb239; - --icon-agent-ask-base: #2090f5; - --icon-agent-build-base: #034cff; - --v2-agent-plan-solid: var(--v2-pink-800); - --v2-agent-plan-border: rgba(200, 61, 139, 0.2); - --v2-agent-plan-background: rgba(253, 236, 243, 0.1); - --v2-agent-build-solid: var(--v2-blue-800); - --v2-agent-build-border: rgba(44, 71, 200, 0.1); - --v2-agent-build-background: rgba(236, 241, 254, 0.1); - --v2-agent-explore-solid: var(--v2-yellow-900); - --v2-agent-explore-border: rgba(203, 159, 52, 0.2); - --v2-agent-explore-background: rgba(254, 250, 236, 0.1); - --v2-agent-review-solid: var(--v2-green-800); - --v2-agent-writer-solid: var(--v2-purple-700); - --icon-on-success-base: rgba(18, 201, 5, 0.9); - --icon-on-success-hover: rgba(45, 186, 38, 0.9); - --icon-on-success-selected: rgba(7, 137, 1, 0.9); - --icon-on-warning-base: rgba(252, 178, 57, 0.9); - --icon-on-warning-hover: rgba(239, 167, 46, 0.9); - --icon-on-warning-selected: rgba(149, 103, 27, 0.9); - --icon-on-critical-base: rgba(252, 83, 58, 0.9); - --icon-on-critical-hover: rgba(237, 72, 49, 0.9); - --icon-on-critical-selected: rgba(202, 45, 23, 0.9); - --icon-on-info-base: #a753ae; - --icon-on-info-hover: rgba(155, 73, 162, 0.9); - --icon-on-info-selected: rgba(155, 77, 161, 0.9); - --icon-diff-add-base: #3a8437; - --icon-diff-add-hover: #1d3e1c; - --icon-diff-add-active: #1d3e1c; - --icon-diff-delete-base: #ed4831; - --icon-diff-delete-hover: #ca2d17; - --icon-diff-modified-base: #ff8c00; - --syntax-comment: var(--text-weak); - --syntax-regexp: var(--text-base); - --syntax-string: #006656; - --syntax-keyword: var(--text-weak); - --syntax-primitive: #fb4804; - --syntax-operator: var(--text-base); - --syntax-variable: var(--text-strong); - --syntax-property: #ed6dc8; - --syntax-type: #596600; - --syntax-constant: #007b80; - --syntax-punctuation: var(--text-base); - --syntax-object: var(--text-strong); - --syntax-success: #2dba26; - --syntax-warning: #efa72e; - --syntax-critical: #ed4831; - --syntax-info: #0092a8; - --syntax-diff-add: #3a8437; - --syntax-diff-delete: #ca2d17; - --syntax-diff-unknown: #ff0000; - --markdown-heading: #d68c27; - --markdown-text: #1a1a1a; - --markdown-link: #3b7dd8; - --markdown-link-text: #318795; - --markdown-code: #3d9a57; - --markdown-block-quote: #b0851f; - --markdown-emph: #b0851f; - --markdown-strong: #d68c27; - --markdown-horizontal-rule: #8a8a8a; - --markdown-list-item: #3b7dd8; - --markdown-list-enumeration: #318795; - --markdown-image: #3b7dd8; - --markdown-image-text: #318795; - --markdown-code-block: #1a1a1a; - --avatar-background-pink: #feeef8; - --avatar-background-mint: #e1fbf4; - --avatar-background-orange: #fff1e7; - --avatar-background-purple: #f9f1fe; - --avatar-background-cyan: #e7f9fb; - --avatar-background-lime: #eefadc; - --avatar-text-pink: #cd1d8d; - --avatar-text-mint: #147d6f; - --avatar-text-orange: #ed5f00; - --avatar-text-purple: #8445bc; - --avatar-text-cyan: #0894b3; - --avatar-text-lime: #5d770d; - --text-stronger: #171717; - - @media (prefers-color-scheme: dark) { - color-scheme: dark; - --text-mix-blend-mode: plus-lighter; - - /* OC-2 fallback variables (dark) */ - --background-base: #101010; - --background-weak: #1e1e1e; - --background-strong: #121212; - --background-stronger: #151515; - --surface-base: rgba(255, 255, 255, 0.031); - --base: rgba(255, 255, 255, 0.034); - --surface-base-hover: rgba(255, 255, 255, 0.039); - --surface-base-active: rgba(255, 255, 255, 0.059); - --surface-base-interactive-active: rgba(3, 76, 255, 0.125); - --base2: rgba(255, 255, 255, 0.034); - --base3: rgba(255, 255, 255, 0.034); - --surface-inset-base: rgba(0, 0, 0, 0.5); - --surface-inset-base-hover: rgba(0, 0, 0, 0.5); - --surface-inset-strong: rgba(0, 0, 0, 0.8); - --surface-inset-strong-hover: rgba(0, 0, 0, 0.8); - --surface-raised-base: rgba(255, 255, 255, 0.059); - --surface-float-base: #161616; - --surface-float-base-hover: #1c1c1c; - --surface-raised-base-hover: rgba(255, 255, 255, 0.078); - --surface-raised-base-active: rgba(255, 255, 255, 0.102); - --surface-raised-strong: rgba(255, 255, 255, 0.078); - --surface-raised-strong-hover: rgba(255, 255, 255, 0.129); - --surface-raised-stronger: rgba(255, 255, 255, 0.129); - --surface-raised-stronger-hover: rgba(255, 255, 255, 0.169); - --surface-weak: rgba(255, 255, 255, 0.078); - --surface-weaker: rgba(255, 255, 255, 0.102); - --surface-strong: rgba(255, 255, 255, 0.169); - --surface-stronger-non-alpha: var(--surface-raised-stronger-non-alpha); - --surface-raised-stronger-non-alpha: #1c1c1c; - --surface-brand-base: #fab283; - --surface-brand-hover: #eda779; - --surface-interactive-base: #091f52; - --surface-interactive-hover: #091f52; - --surface-interactive-weak: #0b1730; - --surface-interactive-weak-hover: #ecf3ff; - --surface-success-base: #062d04; - --surface-success-weak: #0a1e08; - --surface-success-strong: #12c905; - --surface-warning-base: #fdf3cf; - --surface-warning-weak: #fdfaed; - --surface-warning-strong: #fcd53a; - --surface-critical-base: #1f0603; - --surface-critical-weak: #28110c; - --surface-critical-strong: #fc533a; - --surface-info-base: #feecfe; - --surface-info-weak: #fdf7fe; - --surface-info-strong: #edb2f1; - --surface-diff-unchanged-base: #161616; - --surface-diff-skip-base: #00000000; - --surface-diff-hidden-base: #0c1928; - --surface-diff-hidden-weak: #09131d; - --surface-diff-hidden-weaker: #082542; - --surface-diff-hidden-strong: #073966; - --surface-diff-hidden-stronger: #8ec2fc; - --surface-diff-add-base: #1a2919; - --surface-diff-add-weak: #1f351e; - --surface-diff-add-weaker: #1a2919; - --surface-diff-add-strong: #264024; - --surface-diff-add-stronger: #9bcd97; - --surface-diff-delete-base: #42120b; - --surface-diff-delete-weak: #580f06; - --surface-diff-delete-weaker: #42120b; - --surface-diff-delete-strong: #6a1206; - --surface-diff-delete-stronger: #faa494; - --input-base: #1c1c1c; - --input-hover: #1c1c1c; - --input-active: #091123; - --input-selected: #0b1730; - --input-focus: #091123; - --input-disabled: #282828; - --text-base: rgba(255, 255, 255, 0.618); - --text-weak: rgba(255, 255, 255, 0.422); - --text-weaker: rgba(255, 255, 255, 0.284); - --text-strong: rgba(255, 255, 255, 0.936); - --text-invert-base: #a0a0a0; - --text-invert-weak: #707070; - --text-invert-weaker: #505050; - --text-invert-strong: #ededed; - --text-interactive-base: #9dbefe; - --text-on-brand-base: rgba(255, 255, 255, 0.603); - --text-on-interactive-base: #ededed; - --text-on-interactive-weak: rgba(255, 255, 255, 0.603); - --text-on-success-base: #12c905; - --text-on-critical-base: #fc533a; - --text-on-critical-weak: #b72d1a; - --text-on-critical-strong: #ffe0da; - --text-on-warning-base: rgba(255, 255, 255, 0.603); - --text-on-info-base: rgba(255, 255, 255, 0.603); - --text-diff-add-base: #9bcd97; - --text-diff-delete-base: #fc533a; - --text-diff-delete-strong: #ffe0da; - --text-diff-add-strong: #4a7348; - --text-on-info-weak: rgba(255, 255, 255, 0.404); - --text-on-info-strong: rgba(255, 255, 255, 0.928); - --text-on-warning-weak: rgba(255, 255, 255, 0.404); - --text-on-warning-strong: rgba(255, 255, 255, 0.928); - --text-on-success-weak: #127d0d; - --text-on-success-strong: #bafdb3; - --text-on-brand-weak: rgba(255, 255, 255, 0.404); - --text-on-brand-weaker: rgba(255, 255, 255, 0.266); - --text-on-brand-strong: rgba(255, 255, 255, 0.928); - --button-primary-base: #ededed; - --button-secondary-base: #1c1c1c; - --button-secondary-hover: rgba(255, 255, 255, 0.039); - --button-ghost-hover: rgba(255, 255, 255, 0.031); - --button-ghost-hover2: rgba(255, 255, 255, 0.059); - --border-base: rgba(255, 255, 255, 0.195); - --border-hover: rgba(255, 255, 255, 0.284); - --border-active: rgba(255, 255, 255, 0.418); - --border-selected: #9dbefe; - --border-disabled: rgba(255, 255, 255, 0.284); - --border-focus: rgba(255, 255, 255, 0.418); - --border-weak-base: #282828; - --border-strong-base: rgba(255, 255, 255, 0.266); - --border-strong-hover: rgba(255, 255, 255, 0.266); - --border-strong-active: rgba(255, 255, 255, 0.266); - --border-strong-selected: rgba(3, 76, 255, 0.62); - --border-strong-disabled: rgba(255, 255, 255, 0.138); - --border-strong-focus: rgba(255, 255, 255, 0.266); - --border-weak-hover: rgba(255, 255, 255, 0.181); - --border-weak-active: rgba(255, 255, 255, 0.266); - --border-weak-selected: rgba(3, 76, 255, 0.62); - --border-weak-disabled: rgba(255, 255, 255, 0.138); - --border-weak-focus: rgba(255, 255, 255, 0.266); - --border-weaker-base: #202020; - --border-weaker-hover: rgba(255, 255, 255, 0.084); - --border-weaker-active: rgba(255, 255, 255, 0.138); - --border-weaker-selected: rgba(3, 76, 255, 0.32); - --border-weaker-disabled: rgba(255, 255, 255, 0.034); - --border-weaker-focus: rgba(255, 255, 255, 0.138); - --border-interactive-base: #a3c1fd; - --border-interactive-hover: #7ea9ff; - --border-interactive-active: #034cff; - --border-interactive-selected: #034cff; - --border-interactive-disabled: #505050; - --border-interactive-focus: #034cff; - --border-success-base: #96ec8e; - --border-success-hover: #7add71; - --border-success-selected: #12c905; - --border-warning-base: #e9d282; - --border-warning-hover: #dac063; - --border-warning-selected: #fcd53a; - --border-critical-base: #6a1206; - --border-critical-hover: #952414; - --border-critical-selected: #fc533a; - --border-info-base: #eac5ec; - --border-info-hover: #dab1dd; - --border-info-selected: #edb2f1; - --border-color: #ffffff; - --icon-base: #7e7e7e; - --icon-hover: #a0a0a0; - --icon-active: #ededed; - --icon-selected: #ededed; - --icon-disabled: #3e3e3e; - --icon-focus: #ededed; - --icon-invert-base: #161616; - --icon-weak-base: #343434; - --icon-weak-hover: #d9d9d9; - --icon-weak-active: #c8c8c8; - --icon-weak-selected: #707070; - --icon-weak-disabled: #ededed; - --icon-weak-focus: #707070; - --icon-strong-base: #ededed; - --icon-strong-hover: #f6f3f3; - --icon-strong-active: #fcfcfc; - --icon-strong-selected: #fdfcfc; - --icon-strong-disabled: #3e3e3e; - --icon-strong-focus: #fdfcfc; - --icon-brand-base: #ffffff; - --icon-interactive-base: #034cff; - --icon-success-base: #12c905; - --icon-success-hover: #35c02d; - --icon-success-active: #4de144; - --icon-warning-base: #fbb73c; - --icon-warning-hover: #885e08; - --icon-warning-active: #f1b13f; - --icon-critical-base: #fc533a; - --icon-critical-hover: #faa494; - --icon-critical-active: #ffe0da; - --icon-info-base: #68446b; - --icon-info-hover: #815484; - --icon-info-active: #dfa7e3; - --icon-on-brand-base: rgba(255, 255, 255, 0.603); - --icon-on-brand-hover: rgba(255, 255, 255, 0.928); - --icon-on-brand-selected: rgba(255, 255, 255, 0.928); - --icon-on-interactive-base: #ededed; - --icon-agent-plan-base: #edb2f1; - --icon-agent-docs-base: #fbb73c; - --icon-agent-ask-base: #2090f5; - --icon-agent-build-base: #9dbefe; - --v2-agent-plan-solid: var(--v2-pink-400); - --v2-agent-plan-border: rgba(247, 153, 198, 0.2); - --v2-agent-plan-background: rgba(170, 53, 118, 0.05); - --v2-agent-build-solid: var(--v2-blue-300); - --v2-agent-build-border: rgba(162, 188, 255, 0.2); - --v2-agent-build-background: rgba(38, 63, 169, 0.05); - --v2-agent-explore-solid: var(--v2-yellow-300); - --v2-agent-explore-border: rgba(243, 218, 155, 0.2); - --v2-agent-explore-background: rgba(172, 136, 51, 0.05); - --v2-agent-review-solid: var(--v2-green-300); - --v2-agent-writer-solid: var(--v2-purple-400); - --icon-on-success-base: rgba(18, 201, 5, 0.9); - --icon-on-success-hover: rgba(53, 192, 45, 0.9); - --icon-on-success-selected: rgba(77, 225, 68, 0.9); - --icon-on-warning-base: rgba(251, 183, 60, 0.9); - --icon-on-warning-hover: rgba(245, 178, 56, 0.9); - --icon-on-warning-selected: rgba(241, 177, 63, 0.9); - --icon-on-critical-base: rgba(252, 83, 58, 0.9); - --icon-on-critical-hover: rgba(245, 79, 54, 0.9); - --icon-on-critical-selected: rgba(250, 164, 148, 0.9); - --icon-on-info-base: #edb2f1; - --icon-on-info-hover: rgba(231, 173, 235, 0.9); - --icon-on-info-selected: rgba(223, 167, 227, 0.9); - --icon-diff-add-base: #9bcd97; - --icon-diff-add-hover: #c3f9bf; - --icon-diff-add-active: #9bcd97; - --icon-diff-delete-base: #fc533a; - --icon-diff-delete-hover: #f54f36; - --icon-diff-modified-base: #ffba92; - --syntax-comment: var(--text-weak); - --syntax-regexp: var(--text-base); - --syntax-string: #00ceb9; - --syntax-keyword: var(--text-weak); - --syntax-primitive: #ffba92; - --syntax-operator: var(--text-weak); - --syntax-variable: var(--text-strong); - --syntax-property: #ff9ae2; - --syntax-type: #ecf58c; - --syntax-constant: #93e9f6; - --syntax-punctuation: var(--text-weak); - --syntax-object: var(--text-strong); - --syntax-success: #35c02d; - --syntax-warning: #f5b238; - --syntax-critical: #f54f36; - --syntax-info: #93e9f6; - --syntax-diff-add: #9bcd97; - --syntax-diff-delete: #faa494; - --syntax-diff-unknown: #ff0000; - --markdown-heading: #9d7cd8; - --markdown-text: #eeeeee; - --markdown-link: #fab283; - --markdown-link-text: #56b6c2; - --markdown-code: #7fd88f; - --markdown-block-quote: #e5c07b; - --markdown-emph: #e5c07b; - --markdown-strong: #f5a742; - --markdown-horizontal-rule: #808080; - --markdown-list-item: #fab283; - --markdown-list-enumeration: #56b6c2; - --markdown-image: #fab283; - --markdown-image-text: #56b6c2; - --markdown-code-block: #eeeeee; - --avatar-background-pink: #501b3f; - --avatar-background-mint: #033a34; - --avatar-background-orange: #5f2a06; - --avatar-background-purple: #432155; - --avatar-background-cyan: #0f3058; - --avatar-background-lime: #2b3711; - --avatar-text-pink: #e34ba9; - --avatar-text-mint: #95f3d9; - --avatar-text-orange: #ff802b; - --avatar-text-purple: #9d5bd2; - --avatar-text-cyan: #369eff; - --avatar-text-lime: #c4f042; - --text-stronger: rgba(255, 255, 255, 0.936); - } -} diff --git a/packages/ui/src/styles/utilities.css b/packages/ui/src/styles/utilities.css index 3a05a9515fa0..7e9786a8dd80 100644 --- a/packages/ui/src/styles/utilities.css +++ b/packages/ui/src/styles/utilities.css @@ -4,25 +4,85 @@ [data-popper-positioner] { pointer-events: none; } +} + +/* ---- Text selection styling ---- */ + +::selection { + background-color: color-mix(in srgb, var(--border-selected, #034cff) 30%, transparent); + color: var(--text-strong); +} + +/* ---- Global transition defaults for interactive elements ---- */ - /* ::selection { */ - /* background-color: color-mix(in srgb, var(--color-primary) 33%, transparent); */ - /* background-color: var(--color-primary); */ - /* color: var(--color-background); */ - /* } */ +button, +a, +[role="button"], +[data-component="button"], +[data-component="icon-button"], +[data-component="card"], +[data-component="list-item"], +[data-component="tab"], +input, +select, +textarea { + transition-property: background-color, border-color, color, box-shadow, opacity, transform; + transition-duration: var(--duration-fast, 120ms); + transition-timing-function: var(--ease-smooth, cubic-bezier(0.4, 0, 0.2, 1)); } +/* ---- Focus ring utility ---- */ + +:focus-visible { + outline: 2px solid var(--border-selected, #034cff); + outline-offset: 1px; + border-radius: var(--radius-sm); +} + +/* Suppress focus ring inside specific components that handle their own */ +[data-component="dialog"] :focus-visible, +[data-component="text-field"] :focus-visible, +[contenteditable]:focus-visible { + outline: none; +} + +/* ---- Scrollbar styling ---- */ + .no-scrollbar { &::-webkit-scrollbar { display: none; } - /* Hide scrollbar for IE, Edge and Firefox */ & { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; + scrollbar-width: none; } } +/* Thin scrollbar for scroll areas */ +[data-component="scroll-view"], +.thin-scrollbar { + scrollbar-width: thin; + scrollbar-color: var(--surface-weak, #ccc) transparent; + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: var(--surface-weak, #ccc); + border-radius: 3px; + + &:hover { + background-color: var(--surface-weaker, #aaa); + } + } +} + +/* ---- Screen reader only ---- */ + .sr-only { position: absolute; width: 1px; @@ -35,6 +95,8 @@ border-width: 0; } +/* ---- Truncation utilities ---- */ + .truncate-start { text-overflow: ellipsis; overflow: hidden; @@ -43,12 +105,14 @@ text-align: left; } +/* ---- Typography scale ---- */ + .text-12-regular { font-family: var(--font-family-sans); font-size: var(--font-size-small); font-style: normal; font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 166.667% */ + line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); } @@ -57,7 +121,7 @@ font-size: var(--font-size-small); font-style: normal; font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); /* 166.667% */ + line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); } @@ -67,7 +131,7 @@ font-size: var(--font-size-small); font-style: normal; font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 166.667% */ + line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); } @@ -76,7 +140,7 @@ font-size: var(--font-size-base); font-style: normal; font-weight: var(--font-weight-regular); - line-height: var(--line-height-x-large); /* 171.429% */ + line-height: var(--line-height-x-large); letter-spacing: var(--letter-spacing-normal); } @@ -85,7 +149,7 @@ font-size: var(--font-size-base); font-style: normal; font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); /* 171.429% */ + line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); } @@ -95,7 +159,7 @@ font-size: var(--font-size-base); font-style: normal; font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 171.429% */ + line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); } @@ -104,7 +168,7 @@ font-size: var(--font-size-large); font-style: normal; font-weight: var(--font-weight-medium); - line-height: var(--line-height-x-large); /* 150% */ + line-height: var(--line-height-x-large); letter-spacing: var(--letter-spacing-tight); } @@ -113,6 +177,6 @@ font-size: var(--font-size-x-large); font-style: normal; font-weight: var(--font-weight-medium); - line-height: var(--line-height-x-large); /* 120% */ + line-height: var(--line-height-x-large); letter-spacing: var(--letter-spacing-tightest); } diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts deleted file mode 100644 index 5279536f40e8..000000000000 --- a/packages/ui/src/theme/default-themes.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { DesktopTheme } from "./types" -import oc2ThemeJson from "./themes/oc-2.json" -import amoledThemeJson from "./themes/amoled.json" -import auraThemeJson from "./themes/aura.json" -import ayuThemeJson from "./themes/ayu.json" -import carbonfoxThemeJson from "./themes/carbonfox.json" -import catppuccinThemeJson from "./themes/catppuccin.json" -import catppuccinFrappeThemeJson from "./themes/catppuccin-frappe.json" -import catppuccinMacchiatoThemeJson from "./themes/catppuccin-macchiato.json" -import cobalt2ThemeJson from "./themes/cobalt2.json" -import cursorThemeJson from "./themes/cursor.json" -import draculaThemeJson from "./themes/dracula.json" -import everforestThemeJson from "./themes/everforest.json" -import flexokiThemeJson from "./themes/flexoki.json" -import githubThemeJson from "./themes/github.json" -import gruvboxThemeJson from "./themes/gruvbox.json" -import kanagawaThemeJson from "./themes/kanagawa.json" -import lucentOrngThemeJson from "./themes/lucent-orng.json" -import materialThemeJson from "./themes/material.json" -import matrixThemeJson from "./themes/matrix.json" -import mercuryThemeJson from "./themes/mercury.json" -import monokaiThemeJson from "./themes/monokai.json" -import nightowlThemeJson from "./themes/nightowl.json" -import nordThemeJson from "./themes/nord.json" -import oneDarkThemeJson from "./themes/one-dark.json" -import oneDarkProThemeJson from "./themes/onedarkpro.json" -import orngThemeJson from "./themes/orng.json" -import osakaJadeThemeJson from "./themes/osaka-jade.json" -import palenightThemeJson from "./themes/palenight.json" -import rosepineThemeJson from "./themes/rosepine.json" -import shadesOfPurpleThemeJson from "./themes/shadesofpurple.json" -import solarizedThemeJson from "./themes/solarized.json" -import synthwave84ThemeJson from "./themes/synthwave84.json" -import tokyonightThemeJson from "./themes/tokyonight.json" -import vercelThemeJson from "./themes/vercel.json" -import vesperThemeJson from "./themes/vesper.json" -import zenburnThemeJson from "./themes/zenburn.json" - -export const oc2Theme = oc2ThemeJson as DesktopTheme - -export const amoledTheme = amoledThemeJson as DesktopTheme - -export const auraTheme = auraThemeJson as DesktopTheme - -export const ayuTheme = ayuThemeJson as DesktopTheme - -export const carbonfoxTheme = carbonfoxThemeJson as DesktopTheme - -export const catppuccinTheme = catppuccinThemeJson as DesktopTheme - -export const catppuccinFrappeTheme = catppuccinFrappeThemeJson as DesktopTheme - -export const catppuccinMacchiatoTheme = catppuccinMacchiatoThemeJson as DesktopTheme - -export const cobalt2Theme = cobalt2ThemeJson as DesktopTheme - -export const cursorTheme = cursorThemeJson as DesktopTheme - -export const draculaTheme = draculaThemeJson as DesktopTheme - -export const everforestTheme = everforestThemeJson as DesktopTheme - -export const flexokiTheme = flexokiThemeJson as DesktopTheme - -export const githubTheme = githubThemeJson as DesktopTheme - -export const gruvboxTheme = gruvboxThemeJson as DesktopTheme - -export const kanagawaTheme = kanagawaThemeJson as DesktopTheme - -export const lucentOrngTheme = lucentOrngThemeJson as DesktopTheme - -export const materialTheme = materialThemeJson as DesktopTheme - -export const matrixTheme = matrixThemeJson as DesktopTheme - -export const mercuryTheme = mercuryThemeJson as DesktopTheme - -export const monokaiTheme = monokaiThemeJson as DesktopTheme - -export const nightowlTheme = nightowlThemeJson as DesktopTheme - -export const nordTheme = nordThemeJson as DesktopTheme - -export const oneDarkTheme = oneDarkThemeJson as DesktopTheme - -export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme - -export const orngTheme = orngThemeJson as DesktopTheme - -export const osakaJadeTheme = osakaJadeThemeJson as DesktopTheme - -export const palenightTheme = palenightThemeJson as DesktopTheme - -export const rosepineTheme = rosepineThemeJson as DesktopTheme - -export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme - -export const solarizedTheme = solarizedThemeJson as DesktopTheme - -export const synthwave84Theme = synthwave84ThemeJson as DesktopTheme - -export const tokyonightTheme = tokyonightThemeJson as DesktopTheme - -export const vercelTheme = vercelThemeJson as DesktopTheme - -export const vesperTheme = vesperThemeJson as DesktopTheme - -export const zenburnTheme = zenburnThemeJson as DesktopTheme - -export const DEFAULT_THEMES: Record = { - "oc-2": oc2Theme, - amoled: amoledTheme, - aura: auraTheme, - ayu: ayuTheme, - carbonfox: carbonfoxTheme, - catppuccin: catppuccinTheme, - "catppuccin-frappe": catppuccinFrappeTheme, - "catppuccin-macchiato": catppuccinMacchiatoTheme, - cobalt2: cobalt2Theme, - cursor: cursorTheme, - dracula: draculaTheme, - everforest: everforestTheme, - flexoki: flexokiTheme, - github: githubTheme, - gruvbox: gruvboxTheme, - kanagawa: kanagawaTheme, - "lucent-orng": lucentOrngTheme, - material: materialTheme, - matrix: matrixTheme, - mercury: mercuryTheme, - monokai: monokaiTheme, - nightowl: nightowlTheme, - nord: nordTheme, - "one-dark": oneDarkTheme, - onedarkpro: oneDarkProTheme, - orng: orngTheme, - "osaka-jade": osakaJadeTheme, - palenight: palenightTheme, - rosepine: rosepineTheme, - shadesofpurple: shadesOfPurpleTheme, - solarized: solarizedTheme, - synthwave84: synthwave84Theme, - tokyonight: tokyonightTheme, - vercel: vercelTheme, - vesper: vesperTheme, - zenburn: zenburnTheme, -} diff --git a/packages/ui/src/theme/themes/everforest.json b/packages/ui/src/theme/themes/everforest.json deleted file mode 100644 index 21c04c8ab38c..000000000000 --- a/packages/ui/src/theme/themes/everforest.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "$schema": "https://opencode.ai/desktop-theme.json", - "name": "Everforest", - "id": "everforest", - "light": { - "palette": { - "neutral": "#fdf6e3", - "ink": "#5c6a72", - "primary": "#8da101", - "accent": "#df69ba", - "success": "#8da101", - "warning": "#f57d26", - "error": "#f85552", - "info": "#35a77c", - "diffAdd": "#4db380", - "diffDelete": "#f52a65" - }, - "overrides": { - "text-weak": "#a6b0a0", - "syntax-comment": "#a6b0a0", - "syntax-keyword": "#df69ba", - "syntax-string": "#8da101", - "syntax-primitive": "#8da101", - "syntax-variable": "#f85552", - "syntax-property": "#35a77c", - "syntax-type": "#dfa000", - "syntax-constant": "#f57d26", - "syntax-operator": "#35a77c", - "syntax-punctuation": "#5c6a72", - "syntax-object": "#f85552", - "markdown-heading": "#df69ba", - "markdown-text": "#5c6a72", - "markdown-link": "#8da101", - "markdown-link-text": "#35a77c", - "markdown-code": "#8da101", - "markdown-block-quote": "#dfa000", - "markdown-emph": "#dfa000", - "markdown-strong": "#f57d26", - "markdown-horizontal-rule": "#a6b0a0", - "markdown-list-item": "#8da101", - "markdown-list-enumeration": "#35a77c", - "markdown-image": "#8da101", - "markdown-image-text": "#35a77c", - "markdown-code-block": "#5c6a72" - } - }, - "dark": { - "palette": { - "neutral": "#2d353b", - "ink": "#d3c6aa", - "primary": "#a7c080", - "accent": "#d699b6", - "success": "#a7c080", - "warning": "#e69875", - "error": "#e67e80", - "info": "#83c092", - "diffAdd": "#b8db87", - "diffDelete": "#e26a75" - }, - "overrides": { - "text-weak": "#7a8478", - "syntax-comment": "#7a8478", - "syntax-keyword": "#d699b6", - "syntax-string": "#a7c080", - "syntax-primitive": "#a7c080", - "syntax-variable": "#e67e80", - "syntax-property": "#83c092", - "syntax-type": "#dbbc7f", - "syntax-constant": "#e69875", - "syntax-operator": "#83c092", - "syntax-punctuation": "#d3c6aa", - "syntax-object": "#e67e80", - "markdown-heading": "#d699b6", - "markdown-text": "#d3c6aa", - "markdown-link": "#a7c080", - "markdown-link-text": "#83c092", - "markdown-code": "#a7c080", - "markdown-block-quote": "#dbbc7f", - "markdown-emph": "#dbbc7f", - "markdown-strong": "#e69875", - "markdown-horizontal-rule": "#7a8478", - "markdown-list-item": "#a7c080", - "markdown-list-enumeration": "#83c092", - "markdown-image": "#a7c080", - "markdown-image-text": "#83c092", - "markdown-code-block": "#d3c6aa" - } - } -} diff --git a/packages/ui/src/theme/themes/kanagawa.json b/packages/ui/src/theme/themes/kanagawa.json deleted file mode 100644 index 5bfab05bcf06..000000000000 --- a/packages/ui/src/theme/themes/kanagawa.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "$schema": "https://opencode.ai/desktop-theme.json", - "name": "Kanagawa", - "id": "kanagawa", - "light": { - "palette": { - "neutral": "#F2E9DE", - "ink": "#54433A", - "primary": "#2D4F67", - "accent": "#D27E99", - "success": "#6f894e", - "warning": "#cc6d00", - "error": "#E82424", - "info": "#4d699b", - "diffAdd": "#89AF5B", - "diffDelete": "#D61F1F" - }, - "overrides": { - "text-weak": "#8a8980", - "syntax-comment": "#8a8980", - "syntax-keyword": "#624c83", - "syntax-string": "#6f894e", - "syntax-primitive": "#2D4F67", - "syntax-variable": "#54433A", - "syntax-property": "#4d699b", - "syntax-type": "#b35b79", - "syntax-constant": "#cc6d00", - "syntax-operator": "#735896", - "syntax-punctuation": "#54433A", - "syntax-object": "#54433A", - "markdown-heading": "#624c83", - "markdown-text": "#54433A", - "markdown-link": "#2D4F67", - "markdown-link-text": "#4d699b", - "markdown-code": "#6f894e", - "markdown-block-quote": "#8a8980", - "markdown-emph": "#b35b79", - "markdown-strong": "#cc6d00", - "markdown-horizontal-rule": "#8a8980", - "markdown-list-item": "#2D4F67", - "markdown-list-enumeration": "#76946A", - "markdown-image": "#2D4F67", - "markdown-image-text": "#76946A", - "markdown-code-block": "#54433A" - } - }, - "dark": { - "palette": { - "neutral": "#1F1F28", - "ink": "#DCD7BA", - "primary": "#7E9CD8", - "accent": "#D27E99", - "success": "#98BB6C", - "warning": "#D7A657", - "error": "#E82424", - "info": "#76946A", - "diffAdd": "#A9D977", - "diffDelete": "#F24A4A" - }, - "overrides": { - "text-weak": "#727169", - "syntax-comment": "#727169", - "syntax-keyword": "#957FB8", - "syntax-string": "#98BB6C", - "syntax-primitive": "#7E9CD8", - "syntax-variable": "#DCD7BA", - "syntax-property": "#76946A", - "syntax-type": "#C38D9D", - "syntax-constant": "#D7A657", - "syntax-operator": "#D27E99", - "syntax-punctuation": "#DCD7BA", - "syntax-object": "#DCD7BA", - "markdown-heading": "#957FB8", - "markdown-text": "#DCD7BA", - "markdown-link": "#7E9CD8", - "markdown-link-text": "#76946A", - "markdown-code": "#98BB6C", - "markdown-block-quote": "#727169", - "markdown-emph": "#C38D9D", - "markdown-strong": "#D7A657", - "markdown-horizontal-rule": "#727169", - "markdown-list-item": "#7E9CD8", - "markdown-list-enumeration": "#76946A", - "markdown-image": "#7E9CD8", - "markdown-image-text": "#76946A", - "markdown-code-block": "#DCD7BA" - } - } -} diff --git a/packages/ui/src/theme/themes/midnight.json b/packages/ui/src/theme/themes/midnight.json new file mode 100644 index 000000000000..95059620e973 --- /dev/null +++ b/packages/ui/src/theme/themes/midnight.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "Midnight", + "id": "midnight", + "light": { + "seeds": { + "neutral": "#F8FAFC", + "primary": "#1E293B", + "success": "#22C55E", + "warning": "#F59E0B", + "error": "#EF4444", + "info": "#3B82F6", + "interactive": "#3B82F6", + "diffAdd": "#22C55E", + "diffDelete": "#EF4444" + }, + "overrides": { + "background-base": "#F8FAFC", + "background-weak": "#F1F5F9", + "background-strong": "#FFFFFF", + "background-stronger": "#FFFFFF", + "border-weak-base": "#E2E8F0", + "border-weak-hover": "#CBD5E1", + "border-weak-active": "#94A3B8", + "border-weak-selected": "#93C5FD", + "border-weak-disabled": "#F1F5F9", + "border-weak-focus": "#93C5FD", + "border-base": "#CBD5E1", + "border-hover": "#94A3B8", + "border-active": "#64748B", + "border-selected": "#3B82F6", + "border-disabled": "#E2E8F0", + "border-focus": "#3B82F6", + "border-strong-base": "#94A3B8", + "border-strong-hover": "#64748B", + "border-strong-active": "#475569", + "border-strong-selected": "#3B82F6", + "border-strong-disabled": "#CBD5E1", + "border-strong-focus": "#3B82F6", + "surface-diff-add-base": "#DCFCE7", + "surface-diff-delete-base": "#FEE2E2", + "surface-diff-hidden-base": "#DBEAFE", + "text-base": "#334155", + "text-weak": "#64748B", + "text-strong": "#0F172A", + "syntax-string": "#059669", + "syntax-primitive": "#DC2626", + "syntax-property": "#7C3AED", + "syntax-type": "#D97706", + "syntax-constant": "#0284C7", + "syntax-info": "#0284C7", + "markdown-heading": "#1E293B", + "markdown-text": "#334155", + "markdown-link": "#3B82F6", + "markdown-link-text": "#0284C7", + "markdown-code": "#059669", + "markdown-block-quote": "#D97706", + "markdown-emph": "#D97706", + "markdown-strong": "#1E293B", + "markdown-horizontal-rule": "#E2E8F0", + "markdown-list-item": "#3B82F6", + "markdown-list-enumeration": "#0284C7", + "markdown-image": "#3B82F6", + "markdown-image-text": "#0284C7", + "markdown-code-block": "#334155" + } + }, + "dark": { + "seeds": { + "neutral": "#0F172A", + "primary": "#22C55E", + "success": "#22C55E", + "warning": "#F59E0B", + "error": "#EF4444", + "info": "#3B82F6", + "interactive": "#3B82F6", + "diffAdd": "#22C55E", + "diffDelete": "#EF4444" + }, + "overrides": { + "background-base": "#0F172A", + "background-weak": "#131C31", + "background-strong": "#0B1120", + "background-stronger": "#0D1424", + "border-weak-base": "#1E293B", + "border-weak-hover": "#253347", + "border-weak-active": "#334155", + "border-weak-selected": "#1D4ED8", + "border-weak-disabled": "#0F172A", + "border-weak-focus": "#2563EB", + "border-base": "#334155", + "border-hover": "#475569", + "border-active": "#64748B", + "border-selected": "#3B82F6", + "border-disabled": "#1E293B", + "border-focus": "#3B82F6", + "border-strong-base": "#475569", + "border-strong-hover": "#64748B", + "border-strong-active": "#94A3B8", + "border-strong-selected": "#60A5FA", + "border-strong-disabled": "#1E293B", + "border-strong-focus": "#60A5FA", + "surface-diff-add-base": "#052E16", + "surface-diff-delete-base": "#450A0A", + "surface-diff-hidden-base": "#172554", + "text-base": "#CBD5E1", + "text-weak": "#64748B", + "text-strong": "#F8FAFC", + "syntax-string": "#4ADE80", + "syntax-primitive": "#FB7185", + "syntax-property": "#A78BFA", + "syntax-type": "#FBBF24", + "syntax-constant": "#38BDF8", + "syntax-info": "#38BDF8", + "markdown-heading": "#22C55E", + "markdown-text": "#E2E8F0", + "markdown-link": "#60A5FA", + "markdown-link-text": "#38BDF8", + "markdown-code": "#4ADE80", + "markdown-block-quote": "#FBBF24", + "markdown-emph": "#FBBF24", + "markdown-strong": "#F8FAFC", + "markdown-horizontal-rule": "#1E293B", + "markdown-list-item": "#60A5FA", + "markdown-list-enumeration": "#38BDF8", + "markdown-image": "#60A5FA", + "markdown-image-text": "#38BDF8", + "markdown-code-block": "#E2E8F0" + } + } +} diff --git a/packages/ui/src/theme/themes/rosepine.json b/packages/ui/src/theme/themes/rosepine.json deleted file mode 100644 index a71ad18ce000..000000000000 --- a/packages/ui/src/theme/themes/rosepine.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "$schema": "https://opencode.ai/desktop-theme.json", - "name": "Rose Pine", - "id": "rosepine", - "light": { - "palette": { - "neutral": "#faf4ed", - "ink": "#575279", - "primary": "#31748f", - "accent": "#d7827e", - "success": "#286983", - "warning": "#ea9d34", - "error": "#b4637a", - "info": "#56949f" - }, - "overrides": { - "text-weak": "#9893a5", - "syntax-comment": "#9893a5", - "syntax-keyword": "#286983", - "syntax-string": "#ea9d34", - "syntax-primitive": "#d7827e", - "syntax-variable": "#575279", - "syntax-property": "#d7827e", - "syntax-type": "#56949f", - "syntax-constant": "#907aa9", - "syntax-operator": "#797593", - "syntax-punctuation": "#797593", - "syntax-object": "#575279", - "markdown-heading": "#907aa9", - "markdown-text": "#575279", - "markdown-link": "#31748f", - "markdown-link-text": "#d7827e", - "markdown-code": "#286983", - "markdown-block-quote": "#9893a5", - "markdown-emph": "#ea9d34", - "markdown-strong": "#b4637a", - "markdown-horizontal-rule": "#dfdad9", - "markdown-list-item": "#31748f", - "markdown-list-enumeration": "#d7827e", - "markdown-image": "#31748f", - "markdown-image-text": "#d7827e", - "markdown-code-block": "#575279" - } - }, - "dark": { - "palette": { - "neutral": "#191724", - "ink": "#e0def4", - "primary": "#9ccfd8", - "accent": "#ebbcba", - "success": "#31748f", - "warning": "#f6c177", - "error": "#eb6f92", - "info": "#9ccfd8" - }, - "overrides": { - "text-weak": "#6e6a86", - "syntax-comment": "#6e6a86", - "syntax-keyword": "#31748f", - "syntax-string": "#f6c177", - "syntax-primitive": "#ebbcba", - "syntax-variable": "#e0def4", - "syntax-property": "#ebbcba", - "syntax-type": "#9ccfd8", - "syntax-constant": "#c4a7e7", - "syntax-operator": "#908caa", - "syntax-punctuation": "#908caa", - "syntax-object": "#e0def4", - "markdown-heading": "#c4a7e7", - "markdown-text": "#e0def4", - "markdown-link": "#9ccfd8", - "markdown-link-text": "#ebbcba", - "markdown-code": "#31748f", - "markdown-block-quote": "#6e6a86", - "markdown-emph": "#f6c177", - "markdown-strong": "#eb6f92", - "markdown-horizontal-rule": "#403d52", - "markdown-list-item": "#9ccfd8", - "markdown-list-enumeration": "#ebbcba", - "markdown-image": "#9ccfd8", - "markdown-image-text": "#ebbcba", - "markdown-code-block": "#e0def4" - } - } -} diff --git a/packages/web/src/assets/lander/check.svg b/packages/web/src/assets/lander/check.svg index 22de6f2a8832..9dea5c0cd178 100644 --- a/packages/web/src/assets/lander/check.svg +++ b/packages/web/src/assets/lander/check.svg @@ -1,2 +1 @@ - - + \ No newline at end of file diff --git a/packages/web/src/assets/lander/copy.svg b/packages/web/src/assets/lander/copy.svg index f1baac30a02a..10fd19376a8e 100644 --- a/packages/web/src/assets/lander/copy.svg +++ b/packages/web/src/assets/lander/copy.svg @@ -1,2 +1 @@ - - + \ No newline at end of file diff --git a/packages/web/src/assets/logo-dark.svg b/packages/web/src/assets/logo-dark.svg index a1582732423a..9c8e076d266c 100644 --- a/packages/web/src/assets/logo-dark.svg +++ b/packages/web/src/assets/logo-dark.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/web/src/assets/logo-light.svg b/packages/web/src/assets/logo-light.svg index 2a856dccefe8..6e3e80c6a2ed 100644 --- a/packages/web/src/assets/logo-light.svg +++ b/packages/web/src/assets/logo-light.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/references/AionUi b/references/AionUi new file mode 160000 index 000000000000..8a6c0e10da44 --- /dev/null +++ b/references/AionUi @@ -0,0 +1 @@ +Subproject commit 8a6c0e10da4471e6d59ec42497807d359b2376a2 diff --git a/references/Build an AI Agent in Python Boot.dev/Build an AI Agent in Python: Agent Loop | Boot.dev.html b/references/Build an AI Agent in Python Boot.dev/Build an AI Agent in Python: Agent Loop | Boot.dev.html new file mode 100644 index 000000000000..dcf33d3cfccf --- /dev/null +++ b/references/Build an AI Agent in Python Boot.dev/Build an AI Agent in Python: Agent Loop | Boot.dev.html @@ -0,0 +1,30 @@ + + + Learn backend development the smart way | Boot.dev