diff --git a/AGENTS.md b/AGENTS.md index dc72473e..a4b8ab5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,14 +1,17 @@ # OpenChamber - AI Agent Reference (verified) ## Core purpose + OpenChamber provides UI runtimes (web/desktop/VS Code) for interacting with an OpenCode server (local auto-start or remote URL). UI uses HTTP + SSE via `@opencode-ai/sdk`. ## Runtime architecture (IMPORTANT) + - `Desktop` is a thin Tauri shell that starts the web server sidecar and loads the web UI from `http://127.0.0.1:`. - All backend logic lives in `packages/web/server/*` (and `packages/vscode/*` for the VS Code runtime). Desktop Rust is not a feature backend. - Tauri is used only for stable native integrations: menu, dialog (open folder), notifications, updater, deep-links. ## Tech stack (source of truth: `package.json`, resolved: `bun.lock`) + - Runtime/tooling: Bun (`package.json` `packageManager`), Node >=20 (`package.json` `engines`) - UI: React, TypeScript, Vite, Tailwind v4 - State: Zustand (`packages/ui/src/stores/`) @@ -18,55 +21,78 @@ OpenChamber provides UI runtimes (web/desktop/VS Code) for interacting with an O - VS Code: extension + webview (`packages/vscode/`) ## Monorepo layout + Workspaces are `packages/*` (see `package.json`). + - Shared UI: `packages/ui` - Web app + server + CLI: `packages/web` - Desktop app (Tauri): `packages/desktop` - VS Code extension: `packages/vscode` ## Documentation map + Before changing any mapped module, read its module documentation first. ### web + Web runtime and server implementation for OpenChamber. #### lib + Server-side integration modules used by API routes and runtime services. ##### quota + Quota provider registry, dispatch, and provider integrations for usage endpoints. + - Module docs: `packages/web/server/lib/quota/DOCUMENTATION.md` ##### git + Git repository operations for the web server runtime. + - Module docs: `packages/web/server/lib/git/DOCUMENTATION.md` ##### github + GitHub authentication, OAuth device flow, Octokit client factory, and repository URL parsing. + - Module docs: `packages/web/server/lib/github/DOCUMENTATION.md` ##### opencode + OpenCode server integration utilities including config management, provider authentication, and UI authentication. + - Module docs: `packages/web/server/lib/opencode/DOCUMENTATION.md` ##### notifications + Notification message preparation utilities for system notifications, including text truncation and optional summarization. + - Module docs: `packages/web/server/lib/notifications/DOCUMENTATION.md` ##### terminal + WebSocket protocol utilities for terminal input handling including message normalization, control frame parsing, and rate limiting. + - Module docs: `packages/web/server/lib/terminal/DOCUMENTATION.md` ##### tts + Server-side text-to-speech services and summarization helpers for `/api/tts/*` endpoints. + - Module docs: `packages/web/server/lib/tts/DOCUMENTATION.md` ##### skills-catalog + Skills catalog management including discovery, installation, and configuration of agent skill packages. + - Module docs: `packages/web/server/lib/skills-catalog/DOCUMENTATION.md` ## Build / dev commands (verified) + All scripts are in `package.json`. + - Validate: `bun run type-check`, `bun run lint` - Build all: `bun run build` - Desktop build: `bun run desktop:build` @@ -74,6 +100,7 @@ All scripts are in `package.json`. - Release smoke build: `bun run release:test` (shell script: `scripts/test-release-build.sh`) ## Runtime entry points + - Web bootstrap: `packages/web/src/main.tsx` - Web server: `packages/web/server/index.js` - Web CLI: `packages/web/bin/cli.js` (package bin: `packages/web/package.json`) @@ -83,6 +110,7 @@ All scripts are in `package.json`. - VS Code webview bootstrap: `packages/vscode/webview/main.tsx` ## OpenCode integration + - UI client wrapper: `packages/ui/src/lib/opencode/client.ts` (imports `@opencode-ai/sdk/v2`) - SSE hookup: `packages/ui/src/hooks/useEventStream.ts` - Web server embeds/starts OpenCode server: `packages/web/server/index.js` (`createOpencodeServer`) @@ -90,6 +118,7 @@ All scripts are in `package.json`. - External server support: Set `OPENCODE_HOST` (full base URL, e.g. `http://hostname:4096`) or `OPENCODE_PORT`, plus `OPENCODE_SKIP_START=true`, to connect to existing OpenCode instance ## Key UI patterns (reference files) + - Settings shell: `packages/ui/src/components/views/SettingsView.tsx` - Settings shared primitives: `packages/ui/src/components/sections/shared/` - Settings sections: `packages/ui/src/components/sections/` (incl `skills/`) @@ -98,16 +127,19 @@ All scripts are in `package.json`. - Terminal UI: `packages/ui/src/components/terminal/` (uses `ghostty-web`) ## External / system integrations (active) + - Git: `packages/ui/src/lib/gitApi.ts`, `packages/web/server/index.js` (`simple-git`) - Terminal PTY: `packages/web/server/index.js` (`bun-pty`/`node-pty`) - Skills catalog: `packages/web/server/lib/skills-catalog/`, UI: `packages/ui/src/components/sections/skills/` ## Agent constraints + - Do not modify `../opencode` (separate repo). - Do not run git/GitHub commands unless explicitly asked. -- Keep baseline green (run `bun run type-check`, `bun run lint`, `bun run build` before finalizing changes). +- Keep baseline green (run `bun run type-check`, `bun run lint` before finalizing changes). ## Agent code of conduct + - Prefer the smallest correct change. - Preserve working behavior before improving structure. - Do not add cleverness where a direct implementation is enough. @@ -117,6 +149,7 @@ All scripts are in `package.json`. - Finish work end-to-end: implementation, verification, and cleanup. ## Development rules + - Keep diffs tight; avoid drive-by refactors. - Follow local precedent; inspect nearby code before introducing new patterns. - Backend changes: keep web, desktop, and VS Code behavior consistent when they share contracts. @@ -132,28 +165,33 @@ All scripts are in `package.json`. ## Architecture patterns ### Thin entrypoints, focused modules + - Keep orchestration entrypoints thin: `index.js`, bridge files, bootstrap files, provider roots. - Move route, domain, and runtime logic into focused modules with clear ownership. - Prefer dependency injection over hidden module coupling. - Add or update module documentation when ownership changes. ### Strong source of truth + - Prefer deterministic state over heuristics. - Use live server/session state for live activity. Do not let historical anomalies masquerade as current execution. - If a fallback is necessary, scope it narrowly to the active entity and treat it as temporary. - Restore derived UI state from authoritative records. Example: restore model or agent from the latest user message, not assistant-side guesses. ### Live state vs historical state + - Derive live UI behavior from live state channels, not persisted history. - Use historical records to restore context, not to infer that work is still in progress. - If live state is delayed, use the narrowest possible transient fallback and clear it as soon as authoritative state arrives. ### Cross-runtime parity + - If web defines a route or payload contract that shared UI depends on, keep VS Code and desktop parity where applicable. - Shared behavior differences must be intentional and visible in code. - Do not ship a web-only assumption into shared UI. ### Partial-failure-safe flows + - Cross-directory and multi-entity operations must tolerate partial failure. - Prefer per-item results, rollback paths, or resumable cleanup over all-or-nothing assumptions. - Never leave optimistic state or local caches stranded after failure. @@ -196,6 +234,7 @@ are defined in the `clack-cli-patterns` skill and should not be duplicated here. When working on terminal CLI commands, prompts, or output formatting, agents **MUST** study the Clack CLI skill first. **Before starting terminal CLI work:** + ``` skill({ name: "clack-cli-patterns" }) ``` @@ -207,6 +246,7 @@ Scope: terminal CLI only (for example `packages/web/bin/*`). Do not apply this r When working on any UI components, styling, or visual changes, agents **MUST** study the theme system skill first. **Before starting any UI work:** + ``` skill({ name: "theme-system" }) ``` @@ -295,6 +335,7 @@ A single store with N properties means every subscriber re-evaluates on every st - **Pass directory hints when the source of truth isn't available yet.** Newly created sessions aren't in the sync store until SSE delivers them. Pass the known directory as a parameter instead of relying on lookup. ## Regression-prevention checklist + - When adding fallback logic, ask: can stale persisted data keep this path active forever? - When deriving UI state, ask: is this live state, historical state, or inferred state? - When adding store fields, ask: who reads this, how often does it change, and should it live elsewhere? @@ -304,11 +345,13 @@ A single store with N properties means every subscriber re-evaluates on every st - When fixing a bug with a heuristic, prefer narrowing the heuristic over widening it. ## Validation expectations + - Run `bun run type-check`, `bun run lint`, and `bun run build` before finalizing. - For hot-path changes, verify behavior under streaming or repeated events, not just static render. - For sync or startup changes, verify fresh load, retry/failure, and restart behavior. - For session changes, verify create, stream, abort, permission, archive/delete, and revisit flows when relevant. ## Recent changes + - Releases + high-level changes: `CHANGELOG.md` - Recent commits: `git log --oneline` (latest tags: `v1.4.6`, `v1.4.5`) diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index b61102e1..ddac43eb 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -2990,7 +2990,7 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo className={cn( "relative pt-0 pb-4", isDesktopExpanded && 'flex h-full min-h-0 flex-col pt-4', - isMobile && isKeyboardOpen ? "ios-keyboard-safe-area" : "bottom-safe-area" + isMobile && (isKeyboardOpen ? 'ios-keyboard-safe-area' : 'bottom-safe-area') )} data-keyboard-avoid="true" style={isMobile && inputBarOffset > 0 && !isKeyboardOpen ? { marginBottom: `${inputBarOffset}px` } : undefined} diff --git a/packages/ui/src/styles/mobile.css b/packages/ui/src/styles/mobile.css index a63d9768..ec18409b 100644 --- a/packages/ui/src/styles/mobile.css +++ b/packages/ui/src/styles/mobile.css @@ -257,7 +257,7 @@ /* iOS-specific safe area handling with CSS variable support */ @supports (-webkit-touch-callout: none) { /* Safe area handling for fixed positioned elements */ - :root { + :root.device-mobile:not(.desktop-runtime) { --oc-safe-area-top: env(safe-area-inset-top, 0); --oc-safe-area-right: env(safe-area-inset-right, 0); --oc-safe-area-bottom: env(safe-area-inset-bottom, 0); @@ -265,11 +265,11 @@ --oc-safe-area-left: env(safe-area-inset-left, 0); } - .header-safe-area { + :root.device-mobile:not(.desktop-runtime) .header-safe-area { padding-top: var(--oc-safe-area-top); } - .main-content-safe-area { + :root.device-mobile:not(.desktop-runtime) .main-content-safe-area { padding-top: 0; padding-bottom: 0; padding-left: var(--oc-safe-area-left); @@ -277,23 +277,23 @@ } /* Safe area for bottom fixed/bottom-0 elements */ - .bottom-safe-area { + :root.device-mobile:not(.desktop-runtime) .bottom-safe-area { padding-bottom: var(--oc-safe-area-bottom-visual) !important; } /* Drawer safe area - top already offset by header height */ - .drawer-safe-area { + :root.device-mobile:not(.desktop-runtime) .drawer-safe-area { padding-top: 0; padding-bottom: var(--oc-safe-area-bottom-visual); } /* iOS keyboard home indicator safe area - used when keyboard is open */ - .ios-keyboard-safe-area { + :root.device-mobile:not(.desktop-runtime) .ios-keyboard-safe-area { padding-bottom: calc(var(--oc-keyboard-home-indicator, 34px) + var(--oc-safe-area-bottom-visual, 0px)) !important; } /* Prevent keyboard-induced page scroll on iOS PWA */ - body { + :root.device-mobile:not(.desktop-runtime) body { min-height: 100vh; min-height: -webkit-fill-available; position: fixed; @@ -304,24 +304,24 @@ } /* Fix iOS viewport issues */ - .flex.flex-col.h-screen { + :root.device-mobile:not(.desktop-runtime) .flex.flex-col.h-screen { min-height: 100vh; min-height: -webkit-fill-available; } /* Prevent content overlap in iOS */ - .flex-1.overflow-hidden { + :root.device-mobile:not(.desktop-runtime) .flex-1.overflow-hidden { position: relative; overflow-y: auto; -webkit-overflow-scrolling: touch; } /* Update header height for iOS */ - .header-safe-area { + :root.device-mobile:not(.desktop-runtime) .header-safe-area { --header-height: 4.5rem; } - .mobile-sidebar-top { + :root.device-mobile:not(.desktop-runtime) .mobile-sidebar-top { --header-height: 4.5rem; } @@ -358,7 +358,7 @@ /* Fallback for non-iOS standalone mode */ @supports not (-webkit-touch-callout: none) { - :root { + :root.device-mobile:not(.desktop-runtime) { --oc-safe-area-top: env(safe-area-inset-top, 0); --oc-safe-area-right: env(safe-area-inset-right, 0); --oc-safe-area-bottom: env(safe-area-inset-bottom, 0); @@ -366,18 +366,18 @@ --oc-safe-area-left: env(safe-area-inset-left, 0); } - .header-safe-area { + :root.device-mobile:not(.desktop-runtime) .header-safe-area { padding-top: var(--oc-safe-area-top); } - .main-content-safe-area { + :root.device-mobile:not(.desktop-runtime) .main-content-safe-area { padding-top: 0; padding-bottom: 0; padding-left: var(--oc-safe-area-left); padding-right: var(--oc-safe-area-right); } - .bottom-safe-area { + :root.device-mobile:not(.desktop-runtime) .bottom-safe-area { padding-bottom: var(--oc-safe-area-bottom-visual) !important; } } @@ -439,7 +439,7 @@ @media (display-mode: standalone) { /* Home indicator blur overlay */ - body::after { + :root.device-mobile:not(.desktop-runtime) body::after { content: ''; position: fixed; bottom: 0; @@ -462,7 +462,7 @@ /* Fallback for browsers without rgba(from ...) support */ @supports not (color: rgba(from white r g b / 0.5)) { - body::after { + :root.device-mobile:not(.desktop-runtime) body::after { background: linear-gradient( to top, var(--background) 0%,