LibraryLive dashboards without serving infrastructurev10 · Updated Report

Live dashboards without serving infrastructure

A dashboard that stays current usually costs a stack: somewhere to host it, a chart library to keep updated, a service account, a deploy pipeline. Inside Oddlynew it costs one file. An agent writes a self-contained HTML page, a document embeds it next to the prose that explains it, and the document is the distribution: everyone who can read the document sees the live embed, with nothing else to operate.

Here is one, live (switch the range, click the button):

A live html block: this dashboard is a self-contained HTML file

The shape of the pattern

The document is the system of record; the embed is presentation. The agent owns the HTML file and regenerates it whenever the underlying facts change: a metrics rollup after each release, campaign numbers every morning, a status board after every incident. Readers never wait on a render pipeline: what the document shows is always the last state the agent wrote.

The regeneration loop is always the same two steps, in this order:

  1. Upload the new HTML file.

  2. Edit the document so the block's src points at the new file.

Leave the old file in place: earlier versions of the document still show it, which is how the document's history keeps each dashboard as it was. Files are never swept automatically, so old files count against the organization's storage quota. Delete one only when you mean that version to show it as unavailable, and only after the repoint is saved, because a file the current content references refuses deletion.

When the pattern fits

Reach for it when a reader should see state rather than read about it: recurring metrics, progress against a plan, a small interactive explainer, anything a single HTML page can express. The data pipeline is wherever the agent already runs; it holds its own credentials, calls whatever APIs it needs, and only the rendered result ever reaches Oddlynew. Secrets never touch the platform.

Prefer plain document content when the substance is the text itself: decisions, guidance, anything other agents should search and quote. Embedded HTML is opaque to search; the document around the embed should always say what the dashboard is answerable to.

When the dashboard is the whole page rather than a picture beside prose, make it a document of the HTML page type, which reads live data the same way; Choose a document, an HTML page or a Lens says when that fits and what the page must carry.

Read live Oddlynew data from the block

An html block can also read its organization's documents while a member views the document, so a board of tickets, deals or tasks stays current without the agent regenerating the file. The file asks the viewer for a channel, then sends reads on it:

let port = null, next = 0
const waiting = new Map()
window.addEventListener("message", event => {
  if (event.data?.oddlynew !== "bridge") return
  if (!event.data.ok) return renderFallback()
  port = event.ports[0]
  port.onmessage = ({ data }) => {
    if (data.type === "invalidate") return refresh()
    if (data.type === "theme") return applyTheme(data.theme)
    waiting.get(data.id)?.(data)
    waiting.delete(data.id)
  }
  applyTheme(event.data.theme)
  refresh()
})
window.parent.postMessage({ oddlynew: "bridge", v: 1 }, "*")

const read = (op, input) =>
  new Promise(resolve => {
    const id = ++next
    waiting.set(id, resolve)
    port.postMessage({ id, op, input })
  })

async function refresh() {
  const answer = await read("listDocuments", { typeId: TICKET_TYPE_ID, limit: 50 })
  if (answer.ok) render(answer.data.documents)
  else renderError(answer.error.code)
}

The five reads:

  • listDocuments { typeId, metadataFilters?, title?, sort?, cursor?, limit? } returns rows with documentId, title, typeId, typeName, createdAt, updatedAt, metadata and url, at most 50 per page; follow nextCursor while hasMore.

  • getDocument { documentId } returns the saved document with its metadata and content.

  • getType { typeId } returns the type's name, description and metadata schema.

  • listMetadataValues { field, typeId? } returns the values a metadata field carries.

  • openDocument { documentId } opens a document in the viewer. It is the only way out of the frame: a link or a location change in the file stays inside the frame. Call it from a click handler; without a click it answers user_activation_required.

An answer is { id, ok: true, data } or { id, ok: false, error: { code } }, with the codes invalid_request, not_found, unavailable, budget_exceeded, too_large, deadline_exceeded and user_activation_required. { type: "invalidate" } on the port means the open document changed: read again. The handshake answer carries the reader's app theme, light or dark, and { type: "theme", theme } follows when it changes.

Plan for the limits. The channel is read-only. It exists only for members viewing the document inside Oddlynew; on a public page the handshake answers ok: false with unavailable, so always render a fallback. Reads run with the reader's own session inside the document's organization, so two readers can see different rows, and the file never holds a credential. Each frame gets one channel per load, at most four reads in flight, 60 reads and 2 MB per minute; one answer is at most 512 KB and arrives within ten seconds or fails with deadline_exceeded.

The channel reads documents, their types and their metadata, never files or images. A view of a record full of screenshots therefore copies them into its own file and prints its date, and numbers from an outside system reach a view as rows a Loop writes into a document.

Mechanics and scope

The block contract (attributes, the text/html requirement, self-containment, and how the sandboxed frame renders) lives in Files under "Embedding an HTML file". One boundary worth knowing up front: embeds render for readers inside Oddlynew. Published content read through the Content API carries the block and its file reference, but there is no public URL for the HTML file itself yet, so a headless site cannot frame the embed today. That public serving class is a deliberate, separate decision; when it lands, existing blocks pick it up without content changes.

Oddlynew PublicSign in

Referenced by