# Build an App: the Hello App edit-and-reinstall loop

Goal: edit Hello, see the committed change, then create and open a separate My App.
Human version: https://docs.fungi.computer/build/

An App is a git repository with a `package.json` that declares `fungi.app`,
built from an exact commit. No account is needed locally.

## 1. Start a local Oyster

Follow https://docs.fungi.computer/agents/oyster-trial.md until `Oyster is running`
appears in the terminal. Keep that session open. Run the edit and verification
commands in a second terminal, from the directory that holds
`my-oyster`. Set `OYSTER_URL` in that terminal to the Oyster address printed
by the trial. The default is below; replace it if you chose another port.
Keep this terminal for verification and installing My App.

```sh
export OYSTER_URL="http://127.0.0.1:8787"
```

## 2. Find the Hello source

`try` keeps the App it installed as a git repository in the project:

```sh
ls my-oyster/hello-app my-oyster/hello-app/bundle
git -C my-oyster/hello-app log --oneline
```

Expected: `package.json` and `bundle/` (holding `index.html`), and one commit
named `Hello App`. If `my-oyster/hello-app` does not exist, this Oyster
release predates the kept source; see "If it fails".

## 3. Make one visible edit

```sh
node --input-type=module -e 'import { readFileSync, writeFileSync } from "node:fs"; const file = "my-oyster/hello-app/bundle/index.html"; writeFileSync(file, readFileSync(file, "utf8").replace("Hello from my first Oyster App", "Hello from my edited Oyster App"));'
git -C my-oyster/hello-app -c user.name=Me -c user.email=me@example.invalid commit -am "Edit Hello"
```

Keep the rest of the page, especially its `main.js` script: that script tells
Oyster the App is ready. Replacing the whole file with a heading removes it
and leaves the window waiting. Oyster builds only committed source, so commit
the edit before restarting.

## 4. Reinstall

Send Ctrl-C to the trial's terminal session and wait for it to exit. Run the
restart command Oyster prints under `Stopped`: it includes your project path
and the same `--port`. It packs the new commit and replaces the installation,
keeping the App's data. Keep that session running, then return to the second
terminal for verification.

Use the same five-minute startup limit and exit checks as the trial recipe.

Expected: the log shows `Installing the Hello App` again, then `Oyster is running`
and a line `Hello App source:`. Step 5 confirms the new Release.

## 5. Verify

An App's page is served at `/ui/<installation>/<release>/`. Fetch it without a
browser:

```sh
KEY=$(cat my-oyster/.oyster-owner)
REL=$(curl -s -H "Authorization: Bearer $KEY" "$OYSTER_URL/api/installations" | node -e 'const l=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(l.find(i=>i.installationId==="hello").release.releaseId)')
curl -s -H "Authorization: Bearer $KEY" "$OYSTER_URL/ui/hello/$REL/"
```

Expected: `REL` is a new `rel_…` ID and the page prints
`<h1>Hello from my edited Oyster App</h1>`. For the person, give them the new
one-time sign-in link from the terminal (or have them paste
`my-oyster/.oyster-owner` at `$OYSTER_URL/login`) and open
**Hello**. Confirm the new text is visible without a loading or error overlay.
An HTML response alone does not prove the App opened. That is the builder loop: edit, commit, reinstall, see the change.

If it fails:

- `hello-app` is missing, or `try` prints the `install-apps` usage: the
  installed Oyster is older than this recipe. Use the manual pack and install
  guide at https://docs.fungi.computer/oyster/api/#pack-and-build-app-releases for
  `packRelease` and `installRelease`. Stop and report the version mismatch
  before switching workflows.
- `nothing to commit`: the edit did not change the file.
- The page still shows the old text: you reinstalled before committing; commit
  first.

## 6. Create your own App

Keep Oyster running. From the directory containing `my-oyster`, run:

```sh
cd my-oyster && (
set -e
mkdir my-app
cp -R hello-app/bundle my-app/bundle
node --input-type=module <<'JS'
import { writeFileSync } from "node:fs";
writeFileSync("my-app/package.json", JSON.stringify({
  name: "my-app", private: true,
  fungi: { app: { displayName: "My App", icon: "fungi", capabilities: [] } }
}, null, 2) + "\n");
JS
cat > my-app/bundle/index.html <<'HTML'
<!doctype html>
<html lang="en" data-theme="latte">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My App</title>
  <link rel="stylesheet" href="./lesson.css">
</head>
<body>
  <main>
    <h1>This is my own App</h1>
    <p>Edit my-app/bundle/index.html to make this page yours. Keep the main.js script: it tells Oyster this App is ready.</p>
    <p>From my-oyster, commit your edit and install it:</p>
    <pre><code>git -C my-app -c user.name=Me -c user.email=me@example.invalid commit -am "Update My App"
node install-my-app.mjs</code></pre>
    <p>Close this window, refresh the desktop, and open My App again. Oyster can stay running.</p>
  </main>
  <script type="module" src="./main.js"></script>
</body>
</html>
HTML
git -C my-app init -q
git -C my-app add package.json bundle
git -C my-app -c user.name=Me -c user.email=me@example.invalid commit -qm "Create My App"
)
```

Expected: My App has its own Git repository and retains Hello's readiness script.
The command leaves this terminal in `my-oyster`. Stay there for the next step.

## 7. Install and open My App

```sh
cat > install-my-app.mjs <<'JS'
import { execFileSync } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { packRelease } from "@fungi.computer/oyster/pack";

const source = resolve("my-app");
const commit = execFileSync("git", ["-C", source, "rev-parse", "HEAD"], {
  encoding: "utf8",
}).trim();
const out = await mkdtemp(join(tmpdir(), "my-app-release-"));
try {
  console.log("Packing My App from " + commit);
  await packRelease({
    appId: "app_myapp",
    sourceDirectory: source,
    sourceRevision: `git:${commit}`,
    recipe: {
      kind: "bundled-html", entry: "bundle/index.html",
      assets: {
        maxManifestBytes: 65_536, maxObjects: 16,
        maxObjectBytes: 1_048_576, maxTotalBytes: 4_194_304,
      },
      assetBase: "./", backend: null, maxOutputBytes: 4_194_304,
    },
    outDirectory: join(out, "release"),
  });
  execFileSync("npx", ["oyster", "install", join(out, "release"), "--installation-id", "my-app"], {
    stdio: "inherit",
    env: {
      ...process.env,
      OYSTER_URL: process.env.OYSTER_URL ?? "http://127.0.0.1:8787",
      OYSTER_OWNER_KEY: (await readFile(".oyster-owner", "utf8")).trim(),
    },
  });
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
  console.error("Check the committed bundle: git -C my-app ls-tree -r --name-only HEAD");
  console.error("Restore any missing bundle/index.html or main.js, commit, then run node install-my-app.mjs again.");
  console.error("For connection or sign-in errors, check the trial terminal and .oyster-owner in this project.");
  process.exitCode = 1;
} finally {
  await rm(out, { recursive: true, force: true });
}
JS
node install-my-app.mjs
```

Expected: Oyster prints installation progress, a JSON result containing
`"installationId":"my-app"`, and an `Open` link. The script exits 0 on success
for both first installs and updates.
Refresh the signed-in desktop and open **My App**. Verify that **This is my own
App** is visible without a loading or error overlay, and Hello still opens.
Do not substitute an HTTP response for this visible check.

For subsequent edits, commit in `my-app`, rerun `node install-my-app.mjs`,
close the App window, refresh the desktop, and reopen it. No host restart is needed.

If it fails: packing static files does not check JavaScript syntax. A missing
readiness script or syntax error can install successfully but leave the window
waiting. Inspect the browser console; restore `main.js` from
`hello-app/bundle/main.js` and its script tag if removed, commit, install again,
and reopen. A packing error must be fixed before installation; check the terminal
error and the committed files with `git -C my-app ls-tree -r --name-only HEAD`.
Full recovery and custom-port instructions: https://docs.fungi.computer/apps/my-first-app/.

## Going further

For a new React/Caps App with a typed backend, use the published
`create-fungi-app` starter: https://docs.fungi.computer/build/#your-first-app.
For a Computer-backed Inkcap fork with file permissions:
https://docs.fungi.computer/guides/build-an-app/.

## Stop and ask the human when

- a browser sign-in, an email code, a password or a two-factor prompt appears;
- anything asks for payment, a plan upgrade or Team credit;
- a command wants an API key or token that you were not given. Never paste a
  credential into a log, a file in the repository, or a chat message;
- an error is not listed above. Report the exact command and its output.
