
# Remote

Remote puts a real filesystem behind the workbench's tree. It pulls a
[`codelet/server`](/guide/server) directory into the tree, pushes your edits back, and applies
whatever the server reports has changed — so the explorer, the command palette, search and every
language server keep reading one synchronous tree. None of them has to know a server exists.

::code-group

```ts [client.ts]
import { Workbench } from "codelet/workbench";
import { remote } from "codelet/extensions/remote";

const workbench = new Workbench({
  parent: document.getElementById("app")!,
  extensions: [remote({ url: "/api/codelet/" })],
});
```

```ts [server.ts]
import { CodeletServer } from "codelet/server";

export const server = new CodeletServer({ root: "./workspace" });
```

::

`url` is where that server's `fetch` is mounted. See [Server](/guide/server) for routing it in
your framework of choice.

## Options

| Option      | Type                         | Default         | Description                                                                                                                                 |
| ----------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`       | `string`                     | —               | Where the server is mounted. Also the address a binary file is mirrored as.                                                                 |
| `connect`   | `"auto" \| "manual"`         | `"auto"`        | `"manual"` waits for you to enter a URL instead of connecting on activation.                                                                |
| `transport` | `(url: string) => Transport` | `httpTransport` | How to talk to the server. Pass `websocketTransport` to use one socket instead of one request per call.                                     |
| `name`      | `string`                     | `"remote"`      | Names the extension, its commands and its status bar item.                                                                                  |
| `root`      | `string`                     | `"/"`           | The subtree of the server's root to mirror.                                                                                                 |
| `workspace` | `"home" \| "own"`            | `"home"`        | `"home"` mounts the server's files into your own tree. `"own"` opens a tree of its own instead.                                             |
| `mount`     | `string`                     | `"/"`           | Where `root` lands in the workbench's tree. Ignored under `workspace: "own"`.                                                               |
| `watch`     | `boolean`                    | `true`          | Follow the server's change feed. Off leaves the tree as loaded until a manual sync.                                                         |
| `preload`   | `"none" \| "all"`            | `"none"`        | `"none"` fetches a directory level, or a file's text, only once it's opened. `"all"` fetches the whole tree and every file's text up front. |
| `save`      | `"explicit" \| "auto"`       | `"explicit"`    | `"explicit"` pushes a file when you save it. `"auto"` pushes on every change, debounced.                                                    |
| `exclude`   | `string[]`                   | —               | Globs never fetched, applied or pushed. Written against the mirror's own root (`root` on the server, `mount` in the workbench).             |
| `debounce`  | `number`                     | `400`           | Milliseconds an edit waits before it's pushed, when `save` is `"auto"`.                                                                     |

## Connecting manually

By default Remote connects the moment it activates. Pass `connect: "manual"` to wait instead: the
status bar shows a plug icon, and an empty explorer offers a "Remote Connection" prompt asking
where the server is.

```ts
remote({ url: "/api/codelet/", connect: "manual" });
```

Whatever URL you enter there replaces `url` for the rest of the page's life. Click the status bar
item again to connect somewhere else.

## Mounting a subdirectory

`root` and `mount` let a server's tree sit inside a larger workbench rather than take it over.
`root` is the subtree on the server; `mount` is where it lands in the workbench's own tree,
leaving anything else already loaded untouched:

```ts
remote({ url: "/api/codelet/", root: "/src", mount: "/project/src" });
```

Only paths under `mount` are ever read from or pushed to the server — a file the workbench loaded
some other way is left alone. Give the server and the client the same picture: a `mount` other
than the root shows up as a folded, empty folder from the first paint, before anything connects.

To show the server's files on their own, with nothing of the reader's own beside them, pass
`workspace: "own"` instead. It opens a tree of its own — the same mechanism as
[`codelet.workspace.open()`](/api/workspace#workspaces) — the moment the server answers, and
closes it when the extension stops. `mount` still applies inside that tree, but there's nothing
for it to sit beside.

## Excluding files, and loading everything up front

`exclude` keeps globs out of the mirror entirely: never fetched, never watched, never pushed.

```ts
remote({ url: "/api/codelet/", exclude: ["node_modules", "*.lock", ".git"] });
```

By default Remote loads lazily: a directory's children when you expand it, a file's text when you
open it. For a small workspace, fetch the whole tree and every file's text in one request instead:

```ts
remote({ url: "/api/codelet/", preload: "all" });
```

## Using a WebSocket

Pass `websocketTransport` to hold one socket open instead of making a request per call:

```ts
import { remote, websocketTransport } from "codelet/extensions/remote";

remote({ url: "/api/codelet/", transport: websocketTransport });
```

The server needs a matching `WebSocketTransport` mounted beside its `fetch` handler — see
[WebSockets](/guide/server#websockets) on the Server page. One thing still goes over HTTP either
way: a binary file's address, which the [media extension](/extensions/media) resolves as a URL a
socket has no way to give.

## What to know before you ship it

::warning
The protocol carries no authentication. Add a header to every request by wrapping `fetch` through
`httpTransport`'s own `fetch` option, and pass the result as `transport`:

```ts
import { httpTransport, remote } from "codelet/extensions/remote";

remote({
  url: "/api/codelet/",
  transport: (url) =>
    httpTransport(url, {
      fetch: (input, init) =>
        fetch(input, {
          ...init,
          headers: { ...init?.headers, Authorization: `Bearer ${token}` },
        }),
    }),
});
```

This runs for every request the mirror makes, including the change-watching stream.
::

**Binary files** — an image, a video, or anything too large to pull as text — mirror as a URL
rather than as content, which the [media extension](/extensions/media) reads. The mirror never
pushes one of these back, so nothing overwrites the real bytes with a URL. The other direction
does work: an image dropped into the explorer is held as a `data:` URI, and the mirror sends the
bytes that URI stands for, so what lands on the server is the file itself.

::note
The server's own `maxFileSize` (4 MiB by default) is smaller than what the workbench accepts from
a drop (16 MiB): a larger file lands in the tree, is refused by the server with `ETOOBIG`, and
shows up as a row in Problems on that file. Raise `maxFileSize` on the server if you want those to
land.
::

**Readonly files.** A server started readonly, or a file the server can't write, shows locked in
the workbench and refuses local edits before a save is even attempted.

**Connection status** shows in the status bar, labelled with `name`: connecting, connected, stale
(no change events for a while, though nothing has actually failed) or failed with a reason.
Clicking it runs `<name>.reconnect`. A `<name>.sync` command re-fetches everything currently
loaded — reach for it if the server changed while nothing was watching.

:read-more{to="/guide/server"}
