Available

Paste the raw prompt into a model, then describe the document you want. Validate the result before you share it.

Markdown

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.

{
  "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.

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.

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.

"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:

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:

"environmentBindings": [
  { "id": "model", "variable": "MODEL_NAME", "required": false,
    "description": "Default model" }
]
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.