# ワークスペース・アプリの作り方（エージェント向け）

ユーザーから「ワークスペース・アプリを作って」「○○用の GUI を作って」などと頼まれたときの
手順です。専用ツールは不要 — `edit: true` の movement で **Write/Edit を使って HTML を書く**
だけで成立します。

## 何を作るか

ワークスペースの `apps/{name}/index.html` に置く、**自己完結した 1 枚の HTML**（インライン
JS/CSS、`data:` 画像のみ）。ユーザーはワークスペースの「アプリ」タブから「開く」で起動します。
アプリはサンドボックス iframe（opaque origin、`allow-same-origin` 無し）で動き、ファイル I/O は
親への **postMessage ブリッジ経由のみ**です。

## 守るべき制約（重要）

- **外部ネットワーク禁止**: `default-src 'none'; connect-src 'none'` の CSP が自動注入される。
  外部 CDN・外部 API・別ファイルの `<script src>`・Web フォントは読み込めない。すべてインライン化する。
- **資格情報は渡らない**: I/O は親がユーザーのセッションで代理する。ユーザーの権限を超えられない。
- **パスはワークスペース相対**: `output/...`・`apps/{name}/data/...` への書き込みは確認不要。
  それ以外への書き込み・削除はユーザー確認が出る。`..`・絶対パスは拒否される。

## ブリッジ API（このヘルパーをそのまま使うこと）

アプリは `window.parent.postMessage({ ...req, id }, '*')` で要求を送り、応答は `window` の
`message` イベントで受け取る。応答エンベロープは **`{ id, ok: true, data }`** か
**`{ id, ok: false, error }`**。`id` を突き合わせ、`ok` を見て `data` を取り出す。
次の `call()` ヘルパーが正典実装。**改変せずそのままコピーして使う**（自己流で書くと
エンベロープを取り違えて動かない）。

```html
<script>
  let seq = 0;
  const pending = new Map();
  function call(req) {
    return new Promise((resolve, reject) => {
      const id = ++seq;
      pending.set(id, { resolve, reject });
      window.parent.postMessage({ ...req, id }, '*');
    });
  }
  window.addEventListener('message', (e) => {
    const r = e.data;
    const p = pending.get(r && r.id);
    if (!p) return;
    pending.delete(r.id);
    r.ok ? p.resolve(r.data) : p.reject(new Error(r.error));
  });
</script>
```

使い方（`call()` は成功時に応答の `data` を返す）:

```js
const { entries } = await call({ type: 'listFiles', dir: 'output' });   // 一覧
const { content } = await call({ type: 'readFile', path: 'output/x.md' }); // 読み取り
await call({ type: 'writeFile', path: 'output/note.md', content: '...' }); // 書き込み
await call({ type: 'deleteFile', path: 'output/old.txt' });             // 削除（常に確認）
```

確認なしで書ける場所は `output/` 配下と自分のアプリの `apps/{name}/data/` 配下のみ。
それ以外への `writeFile`・すべての `deleteFile` はユーザー確認ダイアログが出る。
`..`・絶対パスは拒否される。

## ひな型をコピーする

ワークスペース内の `readonly/app-templates/` に、複数のアーキタイプテンプレートが
**seed 済み**（workspace-app ピース実行時に自動コピーされる）。Read できる実ファイルなので、
`Glob({ pattern: "readonly/app-templates/*/index.html" })` で一覧を確認できる。

| フォルダ | 用途 |
|---------|------|
| `note-editor/` | `output/note.md` を読み書きするテキストエディタ |
| `data-viewer/` | CSV・JSONL ファイルをテーブル表示するビューア |
| `form-input/` | フォームで入力した内容を `output/` に書き出す |
| `dashboard/` | 複数ファイルの集計結果をカードで表示するダッシュボード |

各フォルダに `index.html`・`app.json`・`e2e.example.json` が入っている。
`e2e.example.json` は後述の E2E テストに直接渡せる入力例。
いちばん近いものを `Read readonly/app-templates/{名前}/index.html` で読み、
`apps/{name}/index.html` に Write するのが最短。テンプレには上記の正しいブリッジ実装が
組み込み済みなので、ファイル I/O もそのまま動く。

`apps/{name}/app.json`（任意）で一覧の表示名・説明・エントリを指定できる（無ければフォルダ名）。

## E2E テスト（verify ステップ）

`workspace-app` ピースの verify ステップは、作成したアプリを **`TestWorkspaceApp` ツールで自動 E2E テスト** する。

- ヘッドレスブラウザでアプリを実際に起動
- `seed_files` で指定した入力データをワークスペースに書き込んでからロード
- `actions` に沿ってクリック・入力・スクリーンショット操作を実行
- `expect` 条件（テキスト含有・ファイル存在・要素可視）を検証

テストに通過した場合のみ verify ステップを完了とする。失敗した場合はエラー内容と
スクリーンショットが返るので、それをもとに HTML を修正して再実行する。

E2E の入力例は各テンプレートの `e2e.example.json` を参照。詳細な使い方は
`ReadToolDoc({ name: "TestWorkspaceApp" })` で取得できる。

## 仕上げ

- 書き込んだら、ユーザーに「アプリ」タブの「開く」で起動できることを一言伝える。
- アプリ名（フォルダ名）は内容が分かる短い英小文字＋ハイフンにする（例: `invoice-gen`）。
