{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://getroomi.com/schema/pitch.json",
  "title": "pitch.json",
  "description": "What a build deployed to Roomi says about itself. Reference: https://getroomi.com/docs/pitch-json",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "const": "container",
          "description": "container: a server, built from the Dockerfile at the root. static: a built folder of files."
        },
        "health": {
          "default": "/healthz",
          "description": "A path that answers 200 once the server is up. A container build only.",
          "type": "string",
          "pattern": "^\\/.*"
        },
        "run": {
          "description": "How the runner starts the build. pitch deploy reads it from the image’s CMD and ENV, so it is rarely written by hand. A container build only.",
          "type": "object",
          "properties": {
            "start": {
              "minItems": 1,
              "maxItems": 64,
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 1000
              },
              "description": "The command that starts the build in /app, as a list of words, like a Dockerfile’s exec-form CMD."
            },
            "env": {
              "description": "Environment variables for the build. Mock settings only: never a secret. The runner sets PORT and HOST.",
              "type": "object",
              "propertyNames": {
                "type": "string",
                "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
              },
              "additionalProperties": {
                "type": "string",
                "maxLength": 4000
              }
            }
          },
          "required": ["start"],
          "additionalProperties": false
        },
        "$schema": {
          "description": "The JSON Schema this file follows, so an editor can complete and check it. pitch init writes it; the platform reads nothing from it.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "The demo’s name, shown in the room. The project’s address is made from it."
        },
        "apps": {
          "minItems": 1,
          "maxItems": 12,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "Names the app everywhere else in pitch.json: lower-case letters, digits and hyphens, unique in apps."
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "description": "What the room calls the app, on its tab in the switcher."
              },
              "path": {
                "type": "string",
                "pattern": "^\\/(?![/\\\\])[^\\s\\\\]*$",
                "description": "Where the app is served on the build's own origin, starting with /."
              },
              "device": {
                "type": "string",
                "enum": ["phone", "tablet", "laptop", "screen", "none"],
                "description": "The frame the room draws the app in, at that device’s real size: phone, tablet, laptop, screen (a wall or a TV), or none."
              },
              "default": {
                "description": "true on the app the room opens first. Without it, the first app opens first.",
                "type": "boolean"
              }
            },
            "required": ["id", "name", "path", "device"],
            "additionalProperties": false
          },
          "description": "Every surface of the build the room can show, one tab each: the client app, the coach’s iPad, the wall."
        },
        "controls": {
          "description": "The launcher’s buttons, lifted into the room. A container build only.",
          "type": "object",
          "properties": {
            "reset": {
              "description": "The call that puts the viewer’s copy back to its seeded start: \"POST /api/demo/reset\".",
              "type": "string",
              "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$"
            },
            "scenarios": {
              "description": "Buttons that drive the demo by hand, one call each. Still honoured; a story is the better way to tell it.",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9-]{0,39}$",
                    "description": "Names the scenario: lower-case letters, digits and hyphens, unique in scenarios."
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "The words on its button in the room."
                  },
                  "description": {
                    "description": "One line under the button, saying what it does.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "call": {
                    "type": "string",
                    "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$",
                    "description": "The call the button makes on the viewer’s copy of the demo: \"METHOD /path\"."
                  },
                  "body": {
                    "description": "A JSON body sent with the call, when the route needs one.",
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                "required": ["id", "label", "call"],
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": false
        },
        "story": {
          "description": "The guided tour the room offers a viewer, in order: what to notice, which app to show and at which page, and what to run first.",
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "Names the step: lower-case letters, digits and hyphens, unique in the story."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "description": "What happens in the step, in a few words."
              },
              "say": {
                "type": "string",
                "minLength": 1,
                "maxLength": 300,
                "description": "What the viewer should notice, and why it matters, in one or two sentences."
              },
              "app": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "The id of the app in apps the step is seen in: where the effect lands."
              },
              "path": {
                "description": "The page of that app the step opens: a path on the build’s own origin, starting with /, such as /transactions/kestrel/overview/. Without it, the step shows the app where the viewer left it. A static build’s path must be a file in its output: the path itself, or index.html inside it.",
                "type": "string",
                "maxLength": 500,
                "pattern": "^\\/(?![/\\\\])[^\\s\\\\]*$"
              },
              "device": {
                "description": "The frame the room draws the app in for this step only: phone, tablet, laptop, screen or none. The app’s own device comes back when the viewer moves on or leaves the tour.",
                "type": "string",
                "enum": ["phone", "tablet", "laptop", "screen", "none"]
              },
              "run": {
                "description": "Calls made in order on the viewer’s own copy before the step shows: \"METHOD /path\", or { \"call\", \"body\" }. A container build only. A step sets up what it needs itself, so a viewer can start at any step.",
                "minItems": 1,
                "maxItems": 8,
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "call": {
                          "type": "string",
                          "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$",
                          "description": "\"METHOD /path\" on the build’s own origin."
                        },
                        "body": {
                          "description": "The JSON body the route needs.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string"
                          },
                          "additionalProperties": {}
                        }
                      },
                      "required": ["call"],
                      "additionalProperties": false
                    }
                  ]
                }
              },
              "point": {
                "description": "A CSS selector on the app’s page for the room to ring. Best-effort: nothing breaks if it is not there. Prefer an id, a data- attribute or a class the demo’s own code names.",
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              }
            },
            "required": ["id", "title", "say", "app"],
            "additionalProperties": false
          }
        },
        "pages": {
          "description": "Markdown or HTML files, relative to pitch.json, uploaded with every deploy and listed in the room’s rail under Read more.",
          "maxItems": 30,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "\\.(md|html)$"
          }
        }
      },
      "required": ["kind", "name", "apps"],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "const": "static",
          "description": "container: a server, built from the Dockerfile at the root. static: a built folder of files."
        },
        "output": {
          "type": "string",
          "minLength": 1,
          "description": "The folder the build writes, relative to pitch.json, with an index.html in it. A static build only."
        },
        "$schema": {
          "description": "The JSON Schema this file follows, so an editor can complete and check it. pitch init writes it; the platform reads nothing from it.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "The demo’s name, shown in the room. The project’s address is made from it."
        },
        "apps": {
          "minItems": 1,
          "maxItems": 12,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "Names the app everywhere else in pitch.json: lower-case letters, digits and hyphens, unique in apps."
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "description": "What the room calls the app, on its tab in the switcher."
              },
              "path": {
                "type": "string",
                "pattern": "^\\/(?![/\\\\])[^\\s\\\\]*$",
                "description": "Where the app is served on the build's own origin, starting with /."
              },
              "device": {
                "type": "string",
                "enum": ["phone", "tablet", "laptop", "screen", "none"],
                "description": "The frame the room draws the app in, at that device’s real size: phone, tablet, laptop, screen (a wall or a TV), or none."
              },
              "default": {
                "description": "true on the app the room opens first. Without it, the first app opens first.",
                "type": "boolean"
              }
            },
            "required": ["id", "name", "path", "device"],
            "additionalProperties": false
          },
          "description": "Every surface of the build the room can show, one tab each: the client app, the coach’s iPad, the wall."
        },
        "controls": {
          "description": "The launcher’s buttons, lifted into the room. A container build only.",
          "type": "object",
          "properties": {
            "reset": {
              "description": "The call that puts the viewer’s copy back to its seeded start: \"POST /api/demo/reset\".",
              "type": "string",
              "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$"
            },
            "scenarios": {
              "description": "Buttons that drive the demo by hand, one call each. Still honoured; a story is the better way to tell it.",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9-]{0,39}$",
                    "description": "Names the scenario: lower-case letters, digits and hyphens, unique in scenarios."
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "The words on its button in the room."
                  },
                  "description": {
                    "description": "One line under the button, saying what it does.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "call": {
                    "type": "string",
                    "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$",
                    "description": "The call the button makes on the viewer’s copy of the demo: \"METHOD /path\"."
                  },
                  "body": {
                    "description": "A JSON body sent with the call, when the route needs one.",
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                "required": ["id", "label", "call"],
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": false
        },
        "story": {
          "description": "The guided tour the room offers a viewer, in order: what to notice, which app to show and at which page, and what to run first.",
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "Names the step: lower-case letters, digits and hyphens, unique in the story."
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "description": "What happens in the step, in a few words."
              },
              "say": {
                "type": "string",
                "minLength": 1,
                "maxLength": 300,
                "description": "What the viewer should notice, and why it matters, in one or two sentences."
              },
              "app": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]{0,39}$",
                "description": "The id of the app in apps the step is seen in: where the effect lands."
              },
              "path": {
                "description": "The page of that app the step opens: a path on the build’s own origin, starting with /, such as /transactions/kestrel/overview/. Without it, the step shows the app where the viewer left it. A static build’s path must be a file in its output: the path itself, or index.html inside it.",
                "type": "string",
                "maxLength": 500,
                "pattern": "^\\/(?![/\\\\])[^\\s\\\\]*$"
              },
              "device": {
                "description": "The frame the room draws the app in for this step only: phone, tablet, laptop, screen or none. The app’s own device comes back when the viewer moves on or leaves the tour.",
                "type": "string",
                "enum": ["phone", "tablet", "laptop", "screen", "none"]
              },
              "run": {
                "description": "Calls made in order on the viewer’s own copy before the step shows: \"METHOD /path\", or { \"call\", \"body\" }. A container build only. A step sets up what it needs itself, so a viewer can start at any step.",
                "minItems": 1,
                "maxItems": 8,
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "string",
                      "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "call": {
                          "type": "string",
                          "pattern": "^(GET|POST|PUT|PATCH|DELETE) \\/\\S*$",
                          "description": "\"METHOD /path\" on the build’s own origin."
                        },
                        "body": {
                          "description": "The JSON body the route needs.",
                          "type": "object",
                          "propertyNames": {
                            "type": "string"
                          },
                          "additionalProperties": {}
                        }
                      },
                      "required": ["call"],
                      "additionalProperties": false
                    }
                  ]
                }
              },
              "point": {
                "description": "A CSS selector on the app’s page for the room to ring. Best-effort: nothing breaks if it is not there. Prefer an id, a data- attribute or a class the demo’s own code names.",
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              }
            },
            "required": ["id", "title", "say", "app"],
            "additionalProperties": false
          }
        },
        "pages": {
          "description": "Markdown or HTML files, relative to pitch.json, uploaded with every deploy and listed in the room’s rail under Read more.",
          "maxItems": 30,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "\\.(md|html)$"
          }
        }
      },
      "required": ["kind", "output", "name", "apps"],
      "additionalProperties": false
    }
  ]
}
