addons/.llms/rules/llms.md
ISL is a web-based UI for the Sapling source control system. It runs as a React client communicating with a Node.js server that wraps the sl CLI. ISL is embedded in VS Code, Android Studio, Visual Studio, Obsidian, and runs standalone in the browser.
eden/addons/
├── isl/ # React client (Vite + TypeScript + Jotai + StyleX)
├── isl-server/ # Node.js server (Rollup + TypeScript)
├── shared/ # Utilities shared between client and server
├── components/ # Reusable UI component library
├── vscode/ # VS Code extension that hosts ISL
├── textmate/ # TextMate grammar files
└── scripts/ # Build and sync scripts
| Task | Command |
|---|---|
| Install dependencies | yarn install (from eden/addons/) |
| Run dev server (browser) | yarn dev |
| Run client tests | cd isl && yarn test |
| Run server tests | cd isl-server && yarn test |
| Run integration tests | cd isl && yarn integration |
| Format code | arc f -a |
| Lint | cd isl && yarn eslint |
Prettier config: single quotes, 2-space indent, 100 print width, trailing commas, no bracket spacing. Imports are auto-organized via prettier-plugin-organize-imports.
ISL uses a typed message bus for bidirectional communication:
type field) in isl/src/types.ts (ClientToServerMessage and ServerToClientMessage).ClientToServerAPI (isl/src/ClientToServerAPI.ts) wraps the MessageBus interface. Use serverAPI.postMessage() to send and serverAPI.onMessageOfType() to listen.ServerToClientAPI (isl-server/src/ServerToClientAPI.ts) handles incoming messages and sends responses.serialize.ts / deserialize handles Map, Set, Date, and Error types over the wire.Subscription pattern: Long-lived data streams (uncommitted changes, smartlog commits, merge conflicts) use a subscription model:
// Client subscribes
{type: 'subscribe', kind: 'smartlogCommits', subscriptionID: '...'}
// Server pushes updates
{type: 'subscriptionResult', kind: 'smartlogCommits', subscriptionID: '...', data: {...}}
When adding new message types, add them to the union types in isl/src/types.ts. The server imports these types from isl via path aliases, ensuring type safety across the boundary.
ISL supports multiple host environments via the Platform interface (isl/src/platform.ts). Each platform provides implementations for file opening, clipboard, theming, persistence, and the message transport.
| Platform | Client Platform | Server Platform |
|---|---|---|
| Browser | BrowserPlatform.ts | chromelikeAppServerPlatform.ts |
| VS Code | vscode/webview/vscodeWebviewPlatform.tsx | webviewServerPlatform.ts |
| Android Studio | Entry via androidStudio.html | androidstudioServerPlatform.ts |
| Visual Studio | Entry via visualStudio.html | visualStudioServerPlatform.ts |
| Obsidian | Entry via obsidian.html | obsidianServerPlatform.ts |
Platform-specific code must go through the Platform interface. Do not import platform implementations directly—use import platform from './platform'.
ISL uses Jotai for all client-side state. Key conventions:
jotaiUtils.ts)| Utility | Purpose |
|---|---|
configBackedAtom | Atom synced with sl config via server messages |
localStorageBackedAtom | Atom persisted to localStorage |
atomWithOnChange | Atom with side-effect callback on value change |
atomFamilyWeak | WeakRef-cached atom family to prevent memory leaks |
lazyAtom | Lazily initialized async atom |
atomResetOnCwdChange | Atom that resets when the working directory changes |
readAtom() and writeAtom() helpers instead of accessing store.get/store.set directly.serverAPI.onMessageOfType() and registerDisposable().useMemo.Repository-mutating commands (commit, amend, rebase, goto, etc.) are modeled as Operation subclasses in isl/src/operations/.
import {Operation} from './Operation';
class MyOperation extends Operation {
constructor(private args: string[]) {
super('MyOperationEvent'); // TrackEventName for analytics
}
getArgs(): Array<CommandArg> {
return ['my-command', ...this.args];
}
// Optional: show optimistic UI while operation runs
optimisticDag(dag: Dag): Dag {
return dag.replaceWith(/* ... */);
}
}
Key methods on Operation:
getArgs() — (required) returns the sl CLI argumentsgetStdin() — optional stdin to pipepreviewDag(dag) — modifies the DAG for pre-confirmation previewoptimisticDag(dag) — modifies the DAG for post-confirmation optimistic statemakeOptimisticUncommittedChangesApplier() — optimistic changes to file statusRunning operations: Use the useRunOperation() hook:
const runOperation = useRunOperation();
runOperation(new RebaseOperation(source, dest));
Operations run one at a time via a queue managed in operationsState.ts.
ISL uses StyleX for component styling.
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
container: {
padding: 'var(--pad)',
display: 'flex',
},
});
// Usage:
<div {...stylex.props(styles.container)} />
components/theme/tokens.stylex.ts.var(--foreground)) for theming.components/ package provides reusable UI primitives (Button, Tooltip, TextField, Dropdown, etc.)—use them instead of raw HTML elements.ts-jest preset, jsdom environment for client tests.@testing-library/react).resetMocks: true in jest config).isl/src/testUtils.tsx)| Helper | Purpose |
|---|---|
simulateMessageFromServer(msg) | Simulate a server→client message |
simulateCommits(commits) | Simulate smartlog commit data |
simulateRepoConnected() | Simulate successful repo connection |
expectMessageSentToServer(msg) | Assert a specific message was sent to server |
expectMessageNOTSentToServer(msg) | Assert a message was NOT sent |
simulateServerDisconnected() | Simulate server disconnection |
COMMIT(hash, title, parent, info?) | Create a commit fixture |
TEST_COMMIT_HISTORY | Standard commit tree fixture for tests |
mockEjeca to simulate sl CLI responses:
mockEjeca([
[/^sl root/, {stdout: '/repo'}],
[/^sl log/, {stdout: '...'}],
]);
WatchForChanges to avoid filesystem/watchman dependencies.shared/testUtils.ts)MockLogger — logger that captures output for assertionsnextTick() — wait for microtask queue to flushclone(obj) — deep clone for test isolation| File | Purpose |
|---|---|
isl/src/types.ts | All core types: message types, commit types, config names |
isl/src/ClientToServerAPI.ts | Client-side typed message API |
isl/src/serverAPIState.ts | Jotai atoms synced with server subscriptions |
isl/src/jotaiUtils.ts | Custom Jotai atom utilities |
isl/src/operationsState.ts | Operation queue and execution state |
isl/src/previews.ts | Optimistic UI / preview state |
isl/src/platform.ts | Platform abstraction interface |
isl/src/serialize.ts | Custom serialization for message bus |
isl/src/dag/ | Commit DAG data structure and rendering |
isl/src/operations/ | All Operation subclasses |
isl/src/codeReview/ | Code review integration (GitHub, Phabricator) |
isl/src/stackEdit/ | Interactive stack editing |
isl/src/CommitInfoView/ | Commit details sidebar |
isl-server/src/Repository.ts | Core server-side repo abstraction |
isl-server/src/OperationQueue.ts | Server-side operation execution |
shared/types/common.ts | Shared types: Hash, RepoPath, Author |
shared/utils.ts | Shared utilities: nullthrows, randomId, defer |
When reviewing ISL changes, flag these issues:
types.tsstore.get/store.set instead of readAtom/writeAtomoptimisticDag when the result is predictablePlatform interfacetestUtils.tsx helperscomponents/ primitives (Button, Tooltip, etc.)[isl] prefix. For example: [isl] Fix optimistic state for rebase operations.