Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ npm test # Run all tests (client + server)
npm run check # Typecheck + test without building (safe while prod runs)
npm run verify # Build + test (catches type errors that vitest misses)
npm run test:coverage # Generate coverage report

# Run a single test file
npx vitest run test/unit/client/components/TabBar.test.tsx
# Server tests use a separate config
npx vitest run --config vitest.server.config.ts test/server/ws-protocol.test.ts
# Run tests matching a name pattern
npx vitest run -t "handles resize"
```

There are **two vitest configs**: `vitest.config.ts` (client, jsdom environment) and `vitest.server.config.ts` (server, node environment). Client tests live in `test/unit/client/`, `test/integration/client/`, and `test/e2e/`. Server tests live in `test/server/`, `test/unit/server/`, and `test/integration/server/`. The default `npm test` runs the client config; `npm run test:all` runs both.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fix AGENTS description of npm test coverage

The new testing note says the default npm test only runs the client Vitest config, but package.json defines test as vitest run && vitest run --config vitest.server.config.ts, so it runs both client and server suites. This mismatch can cause contributors to under-test server changes if they follow AGENTS literally, letting server regressions slip through despite believing they ran the default test workflow.

Useful? React with 👍 / 👎.


### Linting
```bash
npm run lint # ESLint on src/ (includes jsx-a11y)
npm run lint:fix # Auto-fix lint issues
npm run typecheck # Typecheck client + server
```

## Architecture
Expand All @@ -85,16 +101,23 @@ npm run test:coverage # Generate coverage report

### Directory Structure
- `src/` - React frontend application
- `components/` - UI components (TabBar, Sidebar, TerminalView, HistoryView, etc.)
- `store/` - Redux slices (tabs, connection, sessions, settings, claude)
- `lib/` - Utilities (api.ts, claude-types.ts)
- `components/` - UI components (TabBar, Sidebar, TerminalView, panes/, claude-chat/, context-menu/, etc.)
- `store/` - Redux slices, middleware, selectors, and persistence
- `hooks/` - Custom React hooks (activity monitor, notifications, mobile, theme)
- `lib/` - Client utilities (ws-client.ts, api.ts, pane-utils.ts, terminal helpers)
- `server/` - Node.js/Express backend
- `index.ts` - HTTP/REST routes and server entry
- `*-router.ts` - Express route modules (terminals, sessions, settings, ai, debug, files, etc.)
- `ws-handler.ts` - WebSocket protocol handler
- `terminal-registry.ts` - PTY lifecycle management
- `claude-session.ts` - Claude session discovery & indexing
- `claude-indexer.ts` - File watcher for ~/.claude directory
- `test/` - Test suites organized by unit/integration and client/server
- `coding-cli/` - Coding CLI provider abstraction (Claude, Codex) with session indexer and manager
- `session-scanner/` - File watcher service for session discovery and caching
- `sessions-sync/` - Delta sync for session updates to clients
- `tabs-registry/` - Persistent tab/pane state store
- `updater/` - Auto-update version checking
- `shared/` - Code shared between client and server
- `ws-protocol.ts` - Single source of truth for WebSocket message schemas (Zod)
- `path-utils.ts` - Cross-platform path utilities
- `test/` - Tests organized by scope: `unit/client/`, `unit/server/`, `integration/server/`, `integration/client/`, `server/`, `e2e/`

### Key Architectural Patterns

Expand Down Expand Up @@ -146,3 +169,10 @@ All components **must** be accessible for browser-use automation and WCAG compli

- `@/` → `src/`
- `@test/` → `test/`
- `@shared/` → `shared/`

## TypeScript Configs

Two separate configs reflect the client/server split:
- `tsconfig.json` — Client (Bundler resolution, ESNext modules, jsx). Includes `src/` and `shared/`.
- `tsconfig.server.json` — Server (NodeNext resolution, ESM). Server imports must use `.js` extensions.
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
@AGENTS.md
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

@AGENTS.md
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions server/terminal-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ export function buildSpawnSpec(
// Strip inherited env vars that interfere with child terminal behaviour:
// - CLAUDECODE: causes child Claude processes to refuse to start ("nested session" error)
// - CI/NO_COLOR/FORCE_COLOR/COLOR: disables interactive color in user PTYs
// - PORT/VITE_PORT/AUTH_TOKEN/ALLOWED_ORIGINS: server-specific vars that cause
// port conflicts and leak credentials into child processes
// - NODE_ENV/npm_lifecycle_script: server's production env leaks into child shells,
// breaking tools like React test-utils that check NODE_ENV
const {
Expand All @@ -638,6 +640,10 @@ export function buildSpawnSpec(
NO_COLOR: _noColor,
FORCE_COLOR: _forceColor,
COLOR: _color,
PORT: _port,
VITE_PORT: _vitePort,
AUTH_TOKEN: _authToken,
ALLOWED_ORIGINS: _allowedOrigins,
NODE_ENV: _nodeEnv,
npm_lifecycle_script: _npmLifecycleScript,
...parentEnv
Expand Down
22 changes: 22 additions & 0 deletions test/unit/server/terminal-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,26 @@ describe('TerminalRegistry env injection', () => {
expect(env.FRESHELL_TAB_ID).toBe('tab_x')
expect(env.FRESHELL_PANE_ID).toBe('pane_y')
})

it('strips server-specific env vars from child PTY processes', () => {
process.env.PORT = '3001'
process.env.VITE_PORT = '5173'
process.env.AUTH_TOKEN = 'secret-token'
process.env.ALLOWED_ORIGINS = 'http://localhost:3001'
// Sanity: keep a non-stripped var to confirm env is still passed
process.env.HOME = '/home/test'

const registry = new TerminalRegistry()
registry.create({ mode: 'shell', envContext: { tabId: 'tab_1', paneId: 'pane_1' } })

const call = vi.mocked(pty.spawn).mock.calls[0]
const env = call[2].env as Record<string, string>

expect(env.PORT).toBeUndefined()
expect(env.VITE_PORT).toBeUndefined()
expect(env.AUTH_TOKEN).toBeUndefined()
expect(env.ALLOWED_ORIGINS).toBeUndefined()
// Non-stripped vars still pass through
expect(env.HOME).toBe('/home/test')
})
})