Appearance
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 }
endThe 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.
| Call | Available | Hosts | What it does |
|---|---|---|---|
window.graphql(query, variables = {}) | always | all | Runs 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 = {}) | always | all | Runs 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 serverApi | all | Calls a handler in the app's Lua serverApi. Throws when the app declares no serverApi, or the handler errors. |
window.APP_CONTEXT | always | all | { mode, hasServerApi } plus appInfo (id, name, handle, kind) in customApp mode or instance (id, name, integrationId, version) in integration mode. |
window.INTEGRATION_CONTEXT | integrations only | web — not yet on native | Alias of window.APP_CONTEXT, for integration-specific code. |
window.integration.containers.get(schemaName, name) | integrations only | web — not yet on native | Reads a schema-validated data container. Throws when the container does not exist or the read fails. |
window.integration.containers.set(schemaName, name, data) | integrations only | web — not yet on native | Creates or replaces a schema-validated data container. Throws when the data does not satisfy the named schema. |
window.integration.containers.delete(schemaName, name) | integrations only | web — not yet on native | Deletes a data container. Throws when the delete fails. |
Vue bindings in scope
createApp, ref, reactive, computed, watch, onMounted, onUnmounted.
Authoring rules
templateis 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.
scriptruns inside the frame, not on the server. Usewindow.rpcfor 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 sidectx.dataContainer.get/put/delete(key)— persistent key-value storagectx.script.load(name)— load another Lua script by namectx.json.decode(jsonString)— parse JSON into a Lua tablectx.json.encode(value)— serialize a Lua value to a JSON string (functions and cyclic tables raise)ctx.sql.query(sql)— execute MicroSQL reporting queriesctx.ml.predict(modelName, featuresJSON)— ML model inferencectx.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 = "…" })(orrecipient = idinstead ofgroup;bodyHtmloptional) — send an email notification; the older positional formsend(targetIdOrName, options)still works but is deprecatedctx.cache.get(key)/ctx.cache.set({key, value, ttl?})/ctx.cache.delete(key)— tenant-scoped key-value cache (Redis, 4KB limit).settakes 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 navigationDASHBOARD— widget on the dashboardKIOSK— full-screen mode for shop floor displaysJOB_VIEW— embedded in a job detail view