# Share a world

One world for the team: everyone clones from it, everyone pushes to it.

This page is executed as written by `packages/cli/src/journeys/tutorials.test.ts`; the recording
is made from the same run.

![The page, recorded](../media/share-a-world/share-a-world.gif)

A shared world is a world with no app in front of it, served on a URL. It holds the team's
history the way a bare repository does, and every developer's world points at it as origin. This
page runs one on your machine; put it behind TLS on a hostname and nothing else changes.

## The app

```json file=package.json
{ "name": "acme-web", "private": true, "type": "module", "dependencies": { "@octokit/rest": "^21" } }
```

```js file=file-issue.mjs
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data: issue } = await octokit.issues.create({ owner: 'acme', repo: 'web', title: process.argv[2] ?? 'Launch checklist' });
console.log(`filed #${issue.number}`);
```

```bash
npm install
npm install -g @volter/world
npm install -D @volter/twin-github
volter world init
volter world up
```

```text
acme-web  1 twin up, story loaded
```

## The shared world

`init --bare` makes a world with no app: a name, a branch tree per twin, and nothing else.
`serve` puts it on a port and prints the token that opens it. The token is a credential for the
world, never a vendor key; keep it where you keep any team secret.

```bash
mkdir ../team && cd ../team
volter world init --bare acme/team --twins github
volter world serve --port 4300 &
```

```text
serving  acme/team  http://127.0.0.1:4300/acme/team
token    tok_
```

Back in the app:

```bash
cd ../acme-web
```

## Point your world at it, and push

`remote add` records the URL and the token, once. `push` sends the entries your world has that
the shared world does not, as a changeset, and moves your base past them.

```bash
volter remote add origin http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"
volter world run -- node file-issue.mjs
volter world changeset -m "The launch checklist"
volter world push
```

```text
filed #1
changeset  the-launch-checklist  1 change
pushed  the-launch-checklist  1 change → origin
```

## A teammate clones

A second app directory stands in for a teammate's laptop. `clone` records the origin and brings
its whole history in, so the teammate's world holds the issue you filed before they run anything.

```bash
mkdir ../acme-web-two && cp package.json file-issue.mjs ../acme-web-two/ && cd ../acme-web-two
npm install
volter world init
volter world clone http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"
volter world up
volter world log
```

```text
cloned  http://127.0.0.1:4300/acme/team  1 change
github   issue.create   acme/web#1
```

They push too, and you pull:

```bash
volter world run -- node file-issue.mjs "Rotate the keys"
volter world changeset -m "Key rotation"
volter world push
cd ../acme-web
volter world pull
volter world log
```

```text
filed #2
pushed  key-rotation  1 change → origin
pulled  origin  2 changes
github   issue.create   acme/web#1
github   issue.create   acme/web#2
```

`pull` brings in what origin has that you do not and moves your base past it. Your own unpushed
entries stay where they are, on top of the moved base.

## The shared world's own log

The shared world is a world. Its log is the team's history, and it answers the same verbs.

```bash
cd ../team
volter world log
cd ../acme-web
```

```text
github   issue.create   acme/web#1
github   issue.create   acme/web#2
```

## Clean up

```bash
volter world down
kill %1
```

## When the shared world should reach the vendor

A shared world is also where a twin's root can be set to the vendor, so that entries landing there
are deployed with a credential no laptop holds. That is [deploy from a shared world](./deploy-from-a-shared-world.md).

<!-- playback:BEGIN — GENERATED by `bun scripts/docs-media.ts`; do not edit between markers -->

## Playback

Each command above, as the recording shows it.

<details><summary><code>npm install</code></summary>

![step 1](../media/share-a-world/step-01.png)

</details>
<details><summary><code>npm install -g @volter/world</code></summary>

![step 2](../media/share-a-world/step-02.png)

</details>
<details><summary><code>npm install -D @volter/twin-github</code></summary>

![step 3](../media/share-a-world/step-03.png)

</details>
<details><summary><code>volter world init</code></summary>

![step 4](../media/share-a-world/step-04.png)

</details>
<details><summary><code>volter world up</code></summary>

![step 5](../media/share-a-world/step-05.png)

</details>
<details><summary><code>mkdir ../team &amp;&amp; cd ../team</code></summary>

![step 6](../media/share-a-world/step-06.png)

</details>
<details><summary><code>volter world init --bare acme/team --twins github</code></summary>

![step 7](../media/share-a-world/step-07.png)

</details>
<details><summary><code>volter world serve --port 4300 &amp;</code></summary>

![step 8](../media/share-a-world/step-08.png)

</details>
<details><summary><code>cd ../acme-web</code></summary>

![step 9](../media/share-a-world/step-09.png)

</details>
<details><summary><code>volter remote add origin http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"</code></summary>

![step 10](../media/share-a-world/step-10.png)

</details>
<details><summary><code>volter world run -- node file-issue.mjs</code></summary>

![step 11](../media/share-a-world/step-11.png)

</details>
<details><summary><code>volter world changeset -m "The launch checklist"</code></summary>

![step 12](../media/share-a-world/step-12.png)

</details>
<details><summary><code>volter world push</code></summary>

![step 13](../media/share-a-world/step-13.png)

</details>
<details><summary><code>mkdir ../acme-web-two &amp;&amp; cp package.json file-issue.mjs ../acme-web-two/ &amp;&amp; cd ../acme-web-two</code></summary>

![step 14](../media/share-a-world/step-14.png)

</details>
<details><summary><code>npm install</code></summary>

![step 15](../media/share-a-world/step-15.png)

</details>
<details><summary><code>volter world init</code></summary>

![step 16](../media/share-a-world/step-16.png)

</details>
<details><summary><code>volter world clone http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"</code></summary>

![step 17](../media/share-a-world/step-17.png)

</details>
<details><summary><code>volter world up</code></summary>

![step 18](../media/share-a-world/step-18.png)

</details>
<details><summary><code>volter world log</code></summary>

![step 19](../media/share-a-world/step-19.png)

</details>
<details><summary><code>volter world run -- node file-issue.mjs "Rotate the keys"</code></summary>

![step 20](../media/share-a-world/step-20.png)

</details>
<details><summary><code>volter world changeset -m "Key rotation"</code></summary>

![step 21](../media/share-a-world/step-21.png)

</details>
<details><summary><code>volter world push</code></summary>

![step 22](../media/share-a-world/step-22.png)

</details>
<details><summary><code>cd ../acme-web</code></summary>

![step 23](../media/share-a-world/step-23.png)

</details>
<details><summary><code>volter world pull</code></summary>

![step 24](../media/share-a-world/step-24.png)

</details>
<details><summary><code>volter world log</code></summary>

![step 25](../media/share-a-world/step-25.png)

</details>
<details><summary><code>cd ../team</code></summary>

![step 26](../media/share-a-world/step-26.png)

</details>
<details><summary><code>volter world log</code></summary>

![step 27](../media/share-a-world/step-27.png)

</details>
<details><summary><code>cd ../acme-web</code></summary>

![step 28](../media/share-a-world/step-28.png)

</details>
<details><summary><code>volter world down</code></summary>

![step 29](../media/share-a-world/step-29.png)

</details>
<details><summary><code>kill %1</code></summary>

![step 30](../media/share-a-world/step-30.png)

</details>

<!-- playback:END -->
