Back to Sapling

Interactive Smartlog (ISL)

addons/.llms/rules/llms.md

latest11.1 KB
Original Source

Interactive Smartlog (ISL)

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.

Repository Structure

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

Development Commands

TaskCommand
Install dependenciesyarn install (from eden/addons/)
Run dev server (browser)yarn dev
Run client testscd isl && yarn test
Run server testscd isl-server && yarn test
Run integration testscd isl && yarn integration
Format codearc f -a
Lintcd 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.


Architecture

Client-Server Communication

ISL uses a typed message bus for bidirectional communication:

  • Message types are defined as discriminated unions (with a type field) in isl/src/types.ts (ClientToServerMessage and ServerToClientMessage).
  • Client side: ClientToServerAPI (isl/src/ClientToServerAPI.ts) wraps the MessageBus interface. Use serverAPI.postMessage() to send and serverAPI.onMessageOfType() to listen.
  • Server side: ServerToClientAPI (isl-server/src/ServerToClientAPI.ts) handles incoming messages and sends responses.
  • Serialization: Custom 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:

typescript
// 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.

Platform Abstraction

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.

PlatformClient PlatformServer Platform
BrowserBrowserPlatform.tschromelikeAppServerPlatform.ts
VS Codevscode/webview/vscodeWebviewPlatform.tsxwebviewServerPlatform.ts
Android StudioEntry via androidStudio.htmlandroidstudioServerPlatform.ts
Visual StudioEntry via visualStudio.htmlvisualStudioServerPlatform.ts
ObsidianEntry via obsidian.htmlobsidianServerPlatform.ts

Platform-specific code must go through the Platform interface. Do not import platform implementations directly—use import platform from './platform'.


State Management (Jotai)

ISL uses Jotai for all client-side state. Key conventions:

Custom Atom Utilities (jotaiUtils.ts)

UtilityPurpose
configBackedAtomAtom synced with sl config via server messages
localStorageBackedAtomAtom persisted to localStorage
atomWithOnChangeAtom with side-effect callback on value change
atomFamilyWeakWeakRef-cached atom family to prevent memory leaks
lazyAtomLazily initialized async atom
atomResetOnCwdChangeAtom that resets when the working directory changes

Rules

  • Use readAtom() and writeAtom() helpers instead of accessing store.get/store.set directly.
  • Connect atoms to server data via serverAPI.onMessageOfType() and registerDisposable().
  • Derived state should be computed via Jotai derived atoms, not React useMemo.
  • Do not store derived/computed values in writable atoms—use read-only derived atoms instead.

Operations Pattern

Repository-mutating commands (commit, amend, rebase, goto, etc.) are modeled as Operation subclasses in isl/src/operations/.

typescript
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 arguments
  • getStdin() — optional stdin to pipe
  • previewDag(dag) — modifies the DAG for pre-confirmation preview
  • optimisticDag(dag) — modifies the DAG for post-confirmation optimistic state
  • makeOptimisticUncommittedChangesApplier() — optimistic changes to file status

Running operations: Use the useRunOperation() hook:

typescript
const runOperation = useRunOperation();
runOperation(new RebaseOperation(source, dest));

Operations run one at a time via a queue managed in operationsState.ts.


Styling (StyleX)

ISL uses StyleX for component styling.

typescript
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  container: {
    padding: 'var(--pad)',
    display: 'flex',
  },
});

// Usage:
<div {...stylex.props(styles.container)} />
  • Theme tokens are defined in components/theme/tokens.stylex.ts.
  • Use CSS custom properties (e.g., var(--foreground)) for theming.
  • The components/ package provides reusable UI primitives (Button, Tooltip, TextField, Dropdown, etc.)—use them instead of raw HTML elements.

Testing

Test Framework

  • Jest with ts-jest preset, jsdom environment for client tests.
  • React Testing Library for component tests (@testing-library/react).
  • Mocks are reset between tests (resetMocks: true in jest config).

Client Test Helpers (isl/src/testUtils.tsx)

HelperPurpose
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_HISTORYStandard commit tree fixture for tests

Server Test Patterns

  • Mock shell execution with mockEjeca to simulate sl CLI responses:
    typescript
    mockEjeca([
      [/^sl root/, {stdout: '/repo'}],
      [/^sl log/, {stdout: '...'}],
    ]);
    
  • Mock WatchForChanges to avoid filesystem/watchman dependencies.

Shared Test Utilities (shared/testUtils.ts)

  • MockLogger — logger that captures output for assertions
  • nextTick() — wait for microtask queue to flush
  • clone(obj) — deep clone for test isolation

Key Files Reference

FilePurpose
isl/src/types.tsAll core types: message types, commit types, config names
isl/src/ClientToServerAPI.tsClient-side typed message API
isl/src/serverAPIState.tsJotai atoms synced with server subscriptions
isl/src/jotaiUtils.tsCustom Jotai atom utilities
isl/src/operationsState.tsOperation queue and execution state
isl/src/previews.tsOptimistic UI / preview state
isl/src/platform.tsPlatform abstraction interface
isl/src/serialize.tsCustom 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.tsCore server-side repo abstraction
isl-server/src/OperationQueue.tsServer-side operation execution
shared/types/common.tsShared types: Hash, RepoPath, Author
shared/utils.tsShared utilities: nullthrows, randomId, defer

Code Review Guidelines

When reviewing ISL changes, flag these issues:

  • New message types not added to the discriminated unions in types.ts
  • Direct store.get/store.set instead of readAtom/writeAtom
  • Operations that don't implement optimisticDag when the result is predictable
  • Platform-specific code outside the Platform interface
  • Missing test helpers — using raw message simulation instead of testUtils.tsx helpers
  • Synchronous I/O in server code — all file and process operations should be async
  • Inline styles or raw CSS instead of StyleX
  • Raw HTML elements instead of components/ primitives (Button, Tooltip, etc.)

Diff Conventions

  • Diff titles for ISL changes must start with the [isl] prefix. For example: [isl] Fix optimistic state for rebase operations.