# Standalone prompt — build an IDOP 1.0 document

Everything below the line is self-contained. Paste it into any capable model —
ChatGPT, Claude, Gemini, Qwen, a coding agent — **including one that has no
access to this repository**. That is the point of this file: it is the artefact
handed to people running interoperability tests, and it has to work on its own.

If the model *does* have the repository (or the web), point it at the
specification — `docs/spec/1.0/IDOP-1.0.md`, published at
<https://idoplabs.com/spec/idop/1.0/> — which is normative and wins over
anything here. Assistants with the IDOP Cloud connector or the `idop` plugin
already carry an equivalent guide and can validate and save the result
themselves.

---

You are authoring an **IDOP application-document**: a single `.idop` file that
carries its own user interface, its own code, and its own saved state. Someone
opens it in an IDOP Reader; it runs; what they create inside it is saved back
into the same file and travels with it when they send it on.

Build the document the user asks for, following this contract exactly.

## 1. What an `.idop` file is

A ZIP archive with a fixed layout and a strict profile. Produce this tree:

```
mimetype                  first entry, stored uncompressed, exact bytes below
idop.json                 the manifest
code/                     your HTML, CSS, JS — the only executable place
  index.html
  app.js
  styles.css
resources/                optional passive assets: images, fonts, data
storage/                  the document's own saved state, travels with the file
```

`mimetype` contains exactly this, with no trailing newline, and its ZIP entry
has no "extra field" (so the bytes sit at offset 38 of the file):

```
application/vnd.idop+zip
```

Rules the validator enforces. A package breaking any of them is refused before
a single line of it runs:

- Executable content lives **only** under `code/`, and only as `.html`, `.js`,
  `.mjs`, `.css`, `.json`.
- **No inline `<script>`.** Put JavaScript in its own file and load it with
  `<script src="app.js"></script>`.
- **No remote URLs anywhere** — no CDN, no Google Fonts, no external image. A
  document is fully offline except through the network capability in §4.
- **No `<form>`, `<iframe>`, `<object>`, `<embed>`, `<frame>`.** A form can
  navigate the frame away from your document. Use a `<div>` and wire the button
  and the Enter key yourself.
- **No `eval`, `new Function`, `Worker`, `WebAssembly`.**
- No `@import` and no remote URL in CSS.
- No path escaping the package, no absolute path, no symlink, no directory
  entry, no encrypted or ZIP64 archive.

## 2. The manifest

`idop.json`, at the package root. This is the complete shape; fields shown are
required unless marked optional.

```json
{
  "format": "https://idoplabs.com/ns/idop/package",
  "formatVersion": "1.0",
  "runtimeApiVersion": "1.0",
  "entryPoint": "code/index.html",
  "application": {
    "id": "com.example.my-app",
    "version": "1.0.0",
    "title": "My App",
    "description": "One sentence about what it does.",
    "author": "Your name"
  },
  "document": {
    "id": "<uuid v4>",
    "revisionId": "<a different uuid v4>",
    "parentRevisionId": null,
    "createdAt": "2026-10-01T00:00:00Z",
    "modifiedAt": "2026-10-01T00:00:00Z"
  },
  "state": { "schemaVersion": "1.0.0" },
  "requiredFeatures": ["idop.core-storage-v1"],
  "optionalFeatures": [],
  "requiredCapabilities": [],
  "optionalCapabilities": [],
  "environmentBindings": [],
  "credentialBindings": [],
  "extensions": {}
}
```

Constraints that reject a package if broken:

- `format` is exactly `"https://idoplabs.com/ns/idop/package"`, and
  `formatVersion` and `runtimeApiVersion` are **both** `"1.0"`.
- Unknown members are refused, at the top level and inside every object.
- `application.id` is lower-case, dot-separated, at least two segments:
  `^[a-z0-9-]+(\.[a-z0-9-]+)+$`.
- `document.id` and `document.revisionId` are real, *different* UUID v4 values.
  Generate them; do not copy the ones in an example.
- `entryPoint` matches `^code/.+\.html$` and the file must exist.
- Timestamps are RFC 3339 with a `Z`.
- Include `"idop.core-storage-v1"` in `requiredFeatures` if you use storage.
- Every array and object above must be present, even when empty.

## 3. The Runtime API

The Reader injects a frozen `globalThis.idop` before your code runs. It is the
**only** way to reach anything outside your frame. Every method returns a
Promise.

```js
await idop.runtime.getInfo()
// → { runtimeApiVersion, availableCapabilities: ["ai", ...] }
//   Lists what the user actually granted. Check it for optional capabilities.

await idop.storage.read("notes.json")      // → { text }  paths are relative to storage/
await idop.storage.write("notes.json", text)
await idop.storage.delete("notes.json")
await idop.storage.list()                  // → entries in this document's storage
await idop.storage.transaction(operations) // several writes, all or nothing

await idop.env.get("binding-id")           // → { value }  a declared variable only

await idop.network.request({ capability, url, method, headers, body, timeoutMs })
// → { status, headers, body }   body and headers are strings

await idop.ui.requestSave()                // asks the Reader to show its save control
await idop.ui.requestConfiguration()       // asks the Reader to show its settings

await idop.host.getInputFile()             // a viewer document: the one file it was opened for
```

Never do these; they either fail or make the document non-portable:

- `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `navigator.sendBeacon`
- `localStorage`, `sessionStorage`, `indexedDB`, cookies
- deriving any URL from `location`
- asking for a secret's value — no such method exists, by design

**Errors carry a stable `code`.** Branch on `error.code`, never on
`error.message`: the message is translated and may change.

```js
try {
  const response = await idop.network.request({ /* … */ });
} catch (error) {
  if (error.code === 'IDOP-CREDENTIAL-MISSING') { /* ask the user to configure */ }
}
```

Codes you will meet: `IDOP-STORAGE-NOT-FOUND` (normal on first run),
`IDOP-STORAGE-QUOTA`, `IDOP-ENV-MISSING`, `IDOP-ENV-DENIED`,
`IDOP-CREDENTIAL-MISSING`, `IDOP-CREDENTIAL-ORIGIN-DENIED`,
`IDOP-NETWORK-DENIED`, `IDOP-NETWORK-ORIGIN-DENIED`,
`IDOP-NETWORK-METHOD-DENIED`, `IDOP-NETWORK-TIMEOUT`,
`IDOP-NETWORK-BUDGET-EXHAUSTED`, `IDOP-NETWORK-REQUEST-TOO-LARGE`,
`IDOP-NETWORK-RESPONSE-TOO-LARGE`.

## 4. Reaching an external API

Your document has no network. If it needs one, declare a capability and the
Reader makes the call for you, attaching the credential itself.

```json
"requiredCapabilities": [
  {
    "id": "ai",
    "type": "network",
    "origins": ["https://api.example.com"],
    "methods": ["POST"],
    "credentialBinding": "api-key",
    "purpose": "Generate chat responses"
  }
],
"credentialBindings": [
  { "id": "api-key", "required": true,
    "description": "API key for api.example.com" }
]
```

- `type` must be `"network"`. `origins` are exact HTTPS origins — scheme and
  host only, **no path, query or fragment**, and `https` never `http`.
- `methods` from `GET`, `POST`, `PUT`, `PATCH`, `DELETE`.
- `id` and every binding id: lower-case kebab-case, `^[a-z0-9]+(-[a-z0-9]+)*$`.
- A credential binding is only `{ id, required, description? }`. **It names no
  company or service** — a `provider` member is refused. The Reader pairs the
  binding with a key the *user* pinned to the capability's origins (or with a
  well-known service preset it offers, matched the same way, by origin).
- A `credentialBinding` must name a binding declared in `credentialBindings`,
  and every binding must be used by at least one capability, or the package is
  refused.
- `purpose` is shown to the user in the permission prompt. Write a real sentence.

Then call it. Note there is no `Authorization` header — you do not have the key,
and the Reader strips any auth header you set before adding its own:

```js
const response = await idop.network.request({
  capability: 'ai',
  url: 'https://api.example.com/v1/chat/completions',
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ model, messages }),
  timeoutMs: 60_000,
});
if (response.status < 200 || response.status >= 300) {
  // An invalid key answers 401 with a JSON error body and no `choices`.
  // Handle this. Reading `choices[0]` unconditionally is the single most
  // common bug in a first IDOP document.
}
const payload = JSON.parse(response.body);
```

**A credential can only reach its own service.** The Reader holds its own
allowlist of origins per credential, set by the user and never by the document.
Declaring `origins: ["https://my-server.example"]` does not send a key pinned
to another service there — the Reader refuses the request and, if the
capability is required, refuses to open the document at all. Declare the
service's real origin.

The session also has a finite request budget. Do not poll or retry in a loop.

## 5. Environment variables

Non-secret values the user sets in the Reader, which your manifest must declare
before you can read them:

```json
"environmentBindings": [
  { "id": "model", "variable": "MODEL_NAME", "required": false,
    "description": "Default model" }
]
```

```js
try {
  const { value } = await idop.env.get('model');   // the binding id, not the variable name
} catch (error) {
  if (error.code === 'IDOP-ENV-MISSING') { /* use your own default */ }
}
```

`variable` is upper snake case (`^[A-Z][A-Z0-9_]*$`). Mark a binding `required`
only if the document genuinely cannot work without it — a required binding
forces the user to configure it before the document opens. There is no way to
list the user's environment; you see only what you declared.

## 6. Required versus optional

- **Required**: the document cannot do its job without it. The Reader resolves
  it before execution or refuses to open the document.
- **Optional**: the document must run without it. Check
  `(await idop.runtime.getInfo()).availableCapabilities` and degrade gracefully.

Declare the least you need. Every required capability is a prompt the user must
accept, and an honest small ask is more likely to be accepted than a broad one.

## 7. Storage

`storage/**` is the document's state and is what makes an `.idop` worth sending
to someone. It travels with the file; Reader credentials and variables do not.

- Ship a sensible initial file, e.g. `storage/state.json` containing `[]`.
- Treat `IDOP-STORAGE-NOT-FOUND` on first read as empty, not as an error.
- Never write a secret, an API key, or anything you received from a credential
  into storage. It would travel to whoever receives the file.
- Handle `IDOP-STORAGE-QUOTA`: tell the user, do not fail silently.
- Writes reach the *session* immediately and the *file* when the user saves or
  exports. Call `idop.ui.requestSave()` to surface the Reader's save control;
  you cannot write a file yourself.

## 8. Quality bar for the document itself

- Render untrusted text with `textContent`, never `innerHTML` — model output and
  anything loaded from storage counts as untrusted.
- Show a loading state during a network call and disable the control that
  started it.
- Show real errors, mapped from the codes in §3, not `[object Object]`.
- Keep the user's input on screen when a request fails.
- Style with your own CSS in `code/`; respect `prefers-color-scheme`.
- Make it keyboard usable and label your controls.

## 9. Deliver

Produce the complete file tree, then state clearly:

1. Every file and its full contents.
2. The exact `idop.json` you generated, with real UUIDs.
3. Which capabilities, credentials and variables you declared, and why each one
   is required or optional.
4. How to build the archive: `mimetype` first, stored, without extra fields,
   and no directory entries — with Info-ZIP that is
   `zip -X -0 my.idop mimetype` followed by
   `zip -X -D -r my.idop idop.json code resources storage`. Simpler: open
   <https://cloud.idoplabs.com>, which validates the result and says exactly
   what to fix. With the IDOP repository: `idop validate <directory>`, then
   `idop pack <directory> --output my.idop`, then `idop inspect my.idop`.
5. Anything you could not do and why.

Before you deliver, check your own work against this list:

- [ ] `mimetype` is the first entry and is stored, not deflated
- [ ] `mimetype` is `application/vnd.idop+zip` and has no extra field
- [ ] `formatVersion` and `runtimeApiVersion` are both `1.0`
- [ ] `document.id` and `document.revisionId` are distinct real UUID v4s
- [ ] `entryPoint` exists and sits under `code/`
- [ ] no inline `<script>`, no `<form>`, no remote URL, no `eval`
- [ ] every `credentialBinding` names a declared binding; no binding has a `provider`
- [ ] every origin is an exact HTTPS origin with no path
- [ ] no `fetch`, `localStorage` or `location` anywhere in your code
- [ ] no secret is read, logged, or written to storage
- [ ] HTTP error responses are handled, not assumed successful
- [ ] the document does something sensible with no configuration at all

Do not invent manifest fields, capability types, or runtime methods. If
something you need is not in this contract, say so in your answer rather than
inventing it — a file with invented members is refused by every IDOP 1.0
Reader, and a real gap is useful information for the next version.
