Skip to content

Building CoCoCo Custom Apps ​

What are Custom Apps? ​

A custom app has three files:

template — Vue 3 HTML template (Tailwind CSS + DaisyUI pre-bundled)

html
<div class="p-4 space-y-4">
  <h1 class="text-2xl font-bold">{{ title }}</h1>
  <div v-if="loading" class="loading loading-spinner loading-md"></div>
  <div v-for="job in jobs" :key="job.id" class="card bg-base-200 shadow">
    <div class="card-body py-3 px-4">
      <span class="card-title text-sm">{{ job.name }}</span>
      <div class="badge badge-primary badge-sm">{{ job.status }}</div>
    </div>
  </div>
</div>

UI Styling Commandment: You must style custom apps with bundled Tailwind utility classes and DaisyUI components (btn, card, badge, modal, table, loading, etc.). Host theme colors and dark mode are injected automatically; you must respect them so the app feels integrated with the main UI. You must not inject custom CSS (<style>, runtime style tags, or manual CSS strings) unless the user explicitly asks for custom CSS. You must not hardcode custom background/text/border colors unless explicitly requested. Use theme-aware classes like bg-base-100/200/300, text-primary, border-base-300.

script — Vue 3 Composition API (script setup style)

javascript
const jobs = ref([])
const title = ref('My Jobs')
const loading = ref(false)

onMounted(async () => {
  loading.value = true
  const data = await window.graphql(`
    query { listJobs(first: 10) { edges { node { id name status } } } }
  `)
  jobs.value = data.listJobs.edges.map(e => e.node)
  loading.value = false
})

const setupReturn = { jobs, title, loading }

serverApi (optional) — Lua script for server-side RPC handlers. Handlers live on the global exports table; a local M = {} … return M module is not seen by the dispatcher and every call answers "Method not found".

lua
exports = {}

function exports.processJob(input)
  local ok, result = ctx.graphql.query([[
    query GetJob($id: JobID!) { getJob(id: $id) { id name status } }
  ]], { id = input.jobId })
  if not ok then error(result) end
  return { job = result.data.getJob }
end

The API a custom app may call ​

Injected by the host into the app's frame. Nothing else is available: the frame has no ambient network or platform access.

CallAvailableHostsWhat it does
window.graphql(query, variables = {})alwaysallRuns a GraphQL query as the signed-in user and resolves to data. Throws when the request fails or the response carries errors.
window.graphqlMutation(mutation, variables = {})alwaysallRuns a GraphQL mutation as the signed-in user and resolves to data. Throws when the request fails or the response carries errors.
window.rpc(method, ...args)needs a serverApiallCalls a handler in the app's Lua serverApi. Throws when the app declares no serverApi, or the handler errors.
window.APP_CONTEXTalwaysall{ mode, hasServerApi } plus appInfo (id, name, handle, kind) in customApp mode or instance (id, name, integrationId, version) in integration mode.
window.INTEGRATION_CONTEXTintegrations onlyweb — not yet on nativeAlias of window.APP_CONTEXT, for integration-specific code.
window.integration.containers.get(schemaName, name)integrations onlyweb — not yet on nativeReads a schema-validated data container. Throws when the container does not exist or the read fails.
window.integration.containers.set(schemaName, name, data)integrations onlyweb — not yet on nativeCreates or replaces a schema-validated data container. Throws when the data does not satisfy the named schema.
window.integration.containers.delete(schemaName, name)integrations onlyweb — not yet on nativeDeletes a data container. Throws when the delete fails.

Vue bindings in scope ​

createApp, ref, reactive, computed, watch, onMounted, onUnmounted.

Authoring rules ​

  • template is raw HTML with Vue bindings — do not wrap it in <template>…</template>.
  • Styling uses the bundled Tailwind and DaisyUI classes; there is no separate stylesheet.
  • script runs inside the frame, not on the server. Use window.rpc for anything privileged.
  • The frame has no ambient network access: reach the platform through the API below, not fetch.

Lua Server APIs (in serverApi) ​

  • ctx.graphql.query(query, variables) — GraphQL queries from server side
  • ctx.dataContainer.get/put/delete(key) — persistent key-value storage
  • ctx.script.load(name) — load another Lua script by name
  • ctx.json.decode(jsonString) — parse JSON into a Lua table
  • ctx.json.encode(value) — serialize a Lua value to a JSON string (functions and cyclic tables raise)
  • ctx.sql.query(sql) — execute MicroSQL reporting queries
  • ctx.ml.predict(modelName, featuresJSON) — ML model inference
  • ctx.device.http(deviceIdOrName, options) — send HTTP to a device (by ID or name)
  • ctx.device.sql(deviceIdOrName, options) — query a device database (by ID or name)
  • ctx.notify.send({ group = idOrName, subject = "…", body = "…" }) (or recipient = id instead of group; bodyHtml optional) — send an email notification; the older positional form send(targetIdOrName, options) still works but is deprecated
  • ctx.cache.get(key) / ctx.cache.set({key, value, ttl?}) / ctx.cache.delete(key) — tenant-scoped key-value cache (Redis, 4KB limit). set takes a single Lua table; default TTL 1h, max 7d.
  • Standard: math, string, table
  • Not available: os, io, debug, require, load

App Kinds ​

  • PAGE — full-page app in the main navigation
  • DASHBOARD — widget on the dashboard
  • KIOSK — full-screen mode for shop floor displays
  • JOB_VIEW — embedded in a job detail view

Connect machines, automate workflows, and build custom apps for print production.