|
| 1 | +# Behavior Specification |
| 2 | + |
| 3 | +> How CodeWalk behaves from the user's perspective. |
| 4 | +> Only documents **current, implemented** behavior. Planned features live in ROADMAP.md. |
| 5 | +
|
| 6 | +--- |
| 7 | + |
| 8 | +## Onboarding |
| 9 | + |
| 10 | +### First launch shows setup wizard |
| 11 | + |
| 12 | +- **Given** the app is opened for the first time (no servers configured) |
| 13 | +- **When** the app starts |
| 14 | +- **Then** a setup wizard is displayed requiring the user to configure at least one OpenCode server |
| 15 | + |
| 16 | +### No server = no functionality |
| 17 | + |
| 18 | +- **Given** no server is configured |
| 19 | +- **When** the user tries to access any feature |
| 20 | +- **Then** the app blocks access — configuring a server is a prerequisite for all functionality |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Servers |
| 25 | + |
| 26 | +### Multiple server profiles |
| 27 | + |
| 28 | +- **Given** the user is in server settings |
| 29 | +- **When** the user adds a new server profile (local, remote, work, etc.) |
| 30 | +- **Then** the profile is saved and the user can switch between profiles at any time |
| 31 | + |
| 32 | +### Automatic health checks |
| 33 | + |
| 34 | +- **Given** server profiles are configured |
| 35 | +- **When** the app is active |
| 36 | +- **Then** the app periodically checks each server's health and shows a visual online/offline indicator |
| 37 | + |
| 38 | +### Server goes offline during use |
| 39 | + |
| 40 | +- **Given** the active server goes offline while the user is chatting |
| 41 | +- **When** the connection is lost |
| 42 | +- **Then** the composer input is blocked and the reason is displayed to the user |
| 43 | +- **Then** the user cannot send messages until the connection is restored |
| 44 | + |
| 45 | +> **Current state**: the offline error is too aggressive (full-screen error with "Retry"). The desired behavior is a subtle composer block with a clear reason message. |
| 46 | +
|
| 47 | +--- |
| 48 | + |
| 49 | +## Sessions |
| 50 | + |
| 51 | +### Session lifecycle |
| 52 | + |
| 53 | +- **Given** a connected server |
| 54 | +- **When** the user interacts with sessions |
| 55 | +- **Then** the user can **create**, **rename**, **archive**, **fork**, and **delete** sessions |
| 56 | + |
| 57 | +### Fork creates an independent copy |
| 58 | + |
| 59 | +- **Given** an existing session with conversation history |
| 60 | +- **When** the user forks the session |
| 61 | +- **Then** a new independent session is created as a copy from that point — changes to either session do not affect the other |
| 62 | + |
| 63 | +### Sessions are scoped to a project |
| 64 | + |
| 65 | +- **Given** the user has multiple projects/workspaces |
| 66 | +- **When** the user switches to a different project |
| 67 | +- **Then** the visible session list changes to show only sessions belonging to that project |
| 68 | + |
| 69 | +### Auto-generated session titles |
| 70 | + |
| 71 | +- **Given** a new session with no custom title |
| 72 | +- **When** the conversation progresses |
| 73 | +- **Then** the app automatically generates a title based on the conversation content |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## Chat |
| 78 | + |
| 79 | +### Messages are streamed in real time |
| 80 | + |
| 81 | +- **Given** a connected server and an active session |
| 82 | +- **When** the user sends a message |
| 83 | +- **Then** the message is sent to the OpenCode server and the assistant's response streams back via SSE, rendering in real time as text arrives |
| 84 | + |
| 85 | +### User can cancel a response |
| 86 | + |
| 87 | +- **Given** the assistant is actively streaming a response |
| 88 | +- **When** the user taps the cancel/stop button |
| 89 | +- **Then** the response generation stops and the partial response remains visible |
| 90 | + |
| 91 | +### Sending while processing enqueues messages |
| 92 | + |
| 93 | +- **Given** the assistant is actively streaming a response |
| 94 | +- **When** the user sends one or more new messages |
| 95 | +- **Then** each new message is shown in the timeline immediately as queued (with `Queued` state) |
| 96 | +- **Then** the app does not interrupt the running response automatically |
| 97 | + |
| 98 | +### Queued messages are batched into one send |
| 99 | + |
| 100 | +- **Given** there are multiple queued messages for the same session |
| 101 | +- **When** the queue is dispatched (either on first idle opportunity or via `Send now`) |
| 102 | +- **Then** all queued message texts are merged and sent as a single payload |
| 103 | +- **Then** each original queued message becomes one line in that payload (simple `\n` line breaks between messages) |
| 104 | + |
| 105 | +### Send now forces immediate queued dispatch |
| 106 | + |
| 107 | +- **Given** there are queued messages while the assistant is still processing |
| 108 | +- **When** the user taps `Send now` |
| 109 | +- **Then** the app performs the same stop behavior as `Stop` for the active response |
| 110 | +- **Then** as soon as the session becomes ready, the app sends the full queued batch as one payload |
| 111 | + |
| 112 | +### Failed send returns message to composer |
| 113 | + |
| 114 | +- **Given** the user sends a message |
| 115 | +- **When** the send fails (network error, server error, etc.) |
| 116 | +- **Then** the message text is returned to the composer input — the user's text is never lost |
| 117 | + |
| 118 | +### Tool call work groups collapse after completion |
| 119 | + |
| 120 | +- **Given** the assistant executes tool calls during a response (file reads, commands, etc.) |
| 121 | +- **When** the assistant finishes the complete response |
| 122 | +- **Then** the intermediate tool call groups collapse to keep the chat clean |
| 123 | +- **Then** collapse never happens while the assistant is still streaming |
| 124 | + |
| 125 | +### UI remains fluid during streaming |
| 126 | + |
| 127 | +- **Given** the assistant is streaming a long response |
| 128 | +- **When** text, code blocks, or tool calls render incrementally |
| 129 | +- **Then** the UI remains smooth without stuttering, freezing, or perceptible lag |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Composer |
| 134 | + |
| 135 | +### Message history navigation |
| 136 | + |
| 137 | +- **Given** the user has sent previous messages in the session |
| 138 | +- **When** the user presses the up/down arrow keys in the composer |
| 139 | +- **Then** the composer cycles through previously sent messages |
| 140 | + |
| 141 | +### File mentions with @ |
| 142 | + |
| 143 | +- **Given** the user is typing in the composer |
| 144 | +- **When** the user types `@` |
| 145 | +- **Then** a file/context mention picker appears, allowing the user to reference project files in the message |
| 146 | + |
| 147 | +### Slash commands with / |
| 148 | + |
| 149 | +- **Given** the user is typing in the composer |
| 150 | +- **When** the user types `/` |
| 151 | +- **Then** a command picker appears with available slash commands |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Attachments |
| 156 | + |
| 157 | +### Image and PDF attachments |
| 158 | + |
| 159 | +- **Given** the user is composing a message |
| 160 | +- **When** the user attaches an image or PDF |
| 161 | +- **Then** the file is attached to the message and sent along with the text |
| 162 | + |
| 163 | +### Model capability gating |
| 164 | + |
| 165 | +- **Given** the selected model does not support vision |
| 166 | +- **When** the user tries to attach an image |
| 167 | +- **Then** the attachment option is disabled or shows clear feedback that the model cannot process images |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## Voice Input |
| 172 | + |
| 173 | +### Speech-to-text in the composer |
| 174 | + |
| 175 | +- **Given** the user activates voice input |
| 176 | +- **When** the user speaks |
| 177 | +- **Then** the speech is converted to text and inserted into the composer input |
| 178 | + |
| 179 | +### Cross-platform support |
| 180 | + |
| 181 | +- **Given** any supported platform (Android, Linux, macOS, Windows, Web) |
| 182 | +- **When** the user activates voice input |
| 183 | +- **Then** the STT feature works on all platforms where the device has a microphone |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +## Interactive Prompts |
| 188 | + |
| 189 | +### Permission requests |
| 190 | + |
| 191 | +- **Given** the server needs user approval to perform an action (e.g., execute a command, write a file) |
| 192 | +- **When** the server sends a permission request |
| 193 | +- **Then** an interactive card appears in the chat with approve/deny options |
| 194 | +- **Then** the server waits for the user's response before proceeding |
| 195 | + |
| 196 | +### Question prompts |
| 197 | + |
| 198 | +- **Given** the server needs the user to choose between options |
| 199 | +- **When** the server sends a question prompt |
| 200 | +- **Then** an interactive card appears with the question and selectable options |
| 201 | +- **Then** the server waits for the user's response before proceeding |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +## File Explorer |
| 206 | + |
| 207 | +### Read-only project tree |
| 208 | + |
| 209 | +- **Given** the user opens the file explorer panel |
| 210 | +- **When** the project tree loads |
| 211 | +- **Then** the user sees the file/folder structure of the current project in read-only mode (no create, edit, or delete) |
| 212 | + |
| 213 | +### File preview |
| 214 | + |
| 215 | +- **Given** the file explorer is open |
| 216 | +- **When** the user taps a file |
| 217 | +- **Then** a preview/visualization of the file content is shown |
| 218 | + |
| 219 | +--- |
| 220 | + |
| 221 | +## Task List |
| 222 | + |
| 223 | +### Agent-controlled task list |
| 224 | + |
| 225 | +- **Given** the AI agent is executing a multi-step task |
| 226 | +- **When** the agent reports its task progress |
| 227 | +- **Then** a task list is displayed in the session showing the agent's current and completed steps |
| 228 | +- **Then** the task list is read-only for the user — it is controlled entirely by the server/agent |
| 229 | + |
| 230 | +--- |
| 231 | + |
| 232 | +## Layout |
| 233 | + |
| 234 | +### Mobile: chat-first with drawer |
| 235 | + |
| 236 | +- **Given** the app is running on a mobile device (compact screen) |
| 237 | +- **When** the user navigates the app |
| 238 | +- **Then** the chat occupies the full screen, with the session list accessible via a lateral drawer |
| 239 | + |
| 240 | +### Desktop: split view |
| 241 | + |
| 242 | +- **Given** the app is running on a desktop (expanded screen) |
| 243 | +- **When** the user navigates the app |
| 244 | +- **Then** the session list is always visible alongside the chat in a split-view layout |
| 245 | + |
| 246 | +### Keyboard shortcuts |
| 247 | + |
| 248 | +- **Given** a physical keyboard is connected (desktop or mobile with external keyboard) |
| 249 | +- **When** the user presses a keyboard shortcut |
| 250 | +- **Then** the corresponding action is executed (shortcuts work on desktop and on mobile with an external keyboard) |
| 251 | + |
| 252 | +### Mobile keyboard collapses auxiliary panels |
| 253 | + |
| 254 | +- **Given** an auxiliary panel is open (task list, drawer, etc.) on mobile |
| 255 | +- **When** the on-screen keyboard appears |
| 256 | +- **Then** auxiliary panels auto-collapse to maximize available space for the chat and composer |
| 257 | + |
| 258 | +--- |
| 259 | + |
| 260 | +## Settings |
| 261 | + |
| 262 | +### Theme selection |
| 263 | + |
| 264 | +- **Given** the user is in settings |
| 265 | +- **When** the user selects a theme |
| 266 | +- **Then** the app supports light, dark, and AMOLED themes, plus Material You dynamic color from the system wallpaper |
| 267 | + |
| 268 | +### Local persistence |
| 269 | + |
| 270 | +- **Given** the user changes any setting |
| 271 | +- **When** the setting is saved |
| 272 | +- **Then** it persists locally (survives app restart) via SharedPreferences / SecureStorage |
| 273 | + |
| 274 | +--- |
| 275 | + |
| 276 | +## Notifications |
| 277 | + |
| 278 | +### Background notifications |
| 279 | + |
| 280 | +- **Given** the app is in the background |
| 281 | +- **When** the assistant finishes a response → push notification |
| 282 | +- **When** a permission request arrives → push notification |
| 283 | +- **When** a question prompt arrives → push notification |
| 284 | + |
| 285 | +### Server offline does NOT notify |
| 286 | + |
| 287 | +- **Given** the active server goes offline |
| 288 | +- **When** the app detects the disconnection |
| 289 | +- **Then** no push notification is sent — server availability is not the app's responsibility. The user sees the status when they open the app. |
| 290 | + |
| 291 | +### Android persistent notification |
| 292 | + |
| 293 | +- **Given** the app is running on Android |
| 294 | +- **When** the app is active or in background |
| 295 | +- **Then** a persistent notification acts as a tray icon, enabling reliable notification delivery |
| 296 | + |
| 297 | +--- |
| 298 | + |
| 299 | +## Background and Lifecycle |
| 300 | + |
| 301 | +### Android foreground service |
| 302 | + |
| 303 | +- **Given** the app is running on Android during a long operation |
| 304 | +- **When** the app goes to background |
| 305 | +- **Then** a foreground service keeps the app alive so the operation is not killed by the system |
| 306 | + |
| 307 | +### Battery optimization prompt |
| 308 | + |
| 309 | +- **Given** the app is running on Android |
| 310 | +- **When** battery optimization may interfere with background operation |
| 311 | +- **Then** the app prompts the user to disable battery optimization |
| 312 | + |
| 313 | +### Automatic reconnection on resume |
| 314 | + |
| 315 | +- **Given** the app was in background |
| 316 | +- **When** the user returns to the app |
| 317 | +- **Then** the app automatically reconnects to the server and resynchronizes state (missed messages, updated sessions, etc.) |
| 318 | + |
| 319 | +### No duplicate refresh on resume |
| 320 | + |
| 321 | +- **Given** the app resumes from background |
| 322 | +- **When** both lifecycle and reconnect triggers fire |
| 323 | +- **Then** only one refresh cycle executes — no duplicate network calls |
| 324 | + |
| 325 | +--- |
| 326 | + |
| 327 | +## Anti-behaviors |
| 328 | + |
| 329 | +> Things that must **never** happen, regardless of circumstances. |
| 330 | +
|
| 331 | +### Never lose user messages |
| 332 | + |
| 333 | +The app must never silently discard a user's message. If sending fails, the message text returns to the composer input. |
| 334 | + |
| 335 | +### Never freeze the UI |
| 336 | + |
| 337 | +All operations (streaming, sync, network) are asynchronous. The UI must never become unresponsive, even during heavy operations. |
| 338 | + |
| 339 | +### Never expose tokens or credentials |
| 340 | + |
| 341 | +Server tokens, API keys, and credentials must never appear in logs, error screens, exports, or any user-visible surface. |
| 342 | + |
| 343 | +### Never auto-approve permissions |
| 344 | + |
| 345 | +Permission requests from the server always require explicit user action (approve or deny). The app must never approve automatically. |
| 346 | + |
| 347 | +### Never show false aborts |
| 348 | + |
| 349 | +When a connection drops and reconnects (especially on mobile background/resume), the app must not display false "message aborted" errors from stale SSE events. |
| 350 | + |
| 351 | +### Never corrupt state on rapid actions |
| 352 | + |
| 353 | +If the user taps rapidly (double-tap on sessions, fast project switching), the app processes one transition at a time. Concurrent transitions must never corrupt state or cause navigation errors. |
| 354 | + |
| 355 | +### Never cancel responses on session switch |
| 356 | + |
| 357 | +If the assistant is streaming a response and the user switches to a different session, the in-flight response must be preserved — not cancelled. The user can return to the original session and see the completed response. |
| 358 | + |
| 359 | +### Never collapse work groups during streaming |
| 360 | + |
| 361 | +Tool call work groups must only collapse after the assistant has fully completed its response. Premature collapse causes visual flicker and hides active work. |
| 362 | + |
| 363 | +### Never show stale data after resume |
| 364 | + |
| 365 | +When the app returns from background, it must refresh the current session to show the latest state. However, refresh must not re-inject stale abort data that was already handled. |
| 366 | + |
| 367 | +### Never break layout with keyboard |
| 368 | + |
| 369 | +On mobile, the on-screen keyboard must never cause overflow, clipping, or layout breakage. Fixed minimum heights must account for the keyboard-reduced viewport. |
| 370 | + |
| 371 | +### Errors: only show blocking ones |
| 372 | + |
| 373 | +The user should see error feedback only when the error prevents them from continuing (send failed, server unreachable). Non-blocking warnings from the server (partial timeouts, transient issues) should be silent. |
0 commit comments