eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

πŸ€– Views β€” a bot shows you data, in place

Status: built (branch thread-views). The evolution of the terse service-op notice ("Done β€” issues.list on github returned 50 items"): when a bot has list-shaped data worth looking at β€” GitHub issues, news headlines, search results β€” it can now create a view in the chat where you asked.

What a view is (and isn't)

A view is schema + data, never code. It is:

  • A surface the current chat carries. Not an app, not a separate chat, no sidebar entry. It opens from an Open view chip on the bot's reply as a full-height right-hand column (the command panel's side placement β€” a vertical sidebar suits list-shaped content; narrow screens fall back to the in-flow band), and dismisses like any panel.
  • Thread state, like everything else. A view rides the thread's own encrypted log (view.create / view.update / view.remove events), folded client-side by the views registry app and covered by the ordinary app-state snapshots β€” so it syncs to every member and every device, and any bot or app in the chat can read or replace it. The server stores ciphertext, as ever.
  • Rendered by the app schema. The stored spec is expanded (a pure function, viewSchema) into a real app-schema list that the shared schema-engine renders β€” the same machinery that draws To-do and Reminders on web and native. Views inherit search, sections, and rows for free, and the native runtime can render them the day it adds the same expansion.

Why schema+data instead of letting the model write a renderer: the engine is reviewed platform code, so a model-authored view is inert data β€” it can't fetch, can't script, can't touch the DOM. That's why views need no sandbox (unlike member apps, which are code and get the opaque-origin iframe).

The spec (what a bot sends)

{
  "type": "view.create",        // or view.update (replace by id), view.remove
  "id": "vw-a1b2c3",
  "title": "Top GitHub issues", // ≀ 80 chars
  "glyph": "β–€",                 // optional, ≀ 4 chars
  "items": [                    // 1..50
    {
      "id": "1",                // optional; assigned when absent
      "title": "Crash on login",     // required, ≀ 300
      "subtitle": "#412 Β· opened by amy", // optional, ≀ 300
      "note": "Prio: urgent",         // optional, ≀ 500
      "url": "https://github.com/…",  // optional, https only, ≀ 600
      "accent": true                  // optional; tints the subtitle
    }
  ]
}

Bounded twice, fail-closed both times: the sidecar validates the model's create_view tool input (internal/bot/view.go, modeled on normalizeCard) before anything posts, and the client fold re-validates every event independently (web/static/apps/views.js) β€” payloads aren't sender-signed, so the fold trusts nothing: strings clamped, non-https URLs dropped, ≀ 50 items, whole spec ≀ 32 KB, ≀ 20 live views per thread (oldest evicted). A row's tap opens its url in a new tab (rel=noopener); rows without one aren't tappable. Everything renders via textContent β€” a view can display data and link out, nothing else.

How Eleven creates one

create_view is a bot tool in two places:

  • The research loop (internal/bot/research.go) β€” the main path. "List the 5 highest priority GitHub issues" β†’ the member taps the research consent chip β†’ the loop reads issues.list under the short-lived grant, the model sorts, calls create_view, and the sidecar posts the view.create event. Validation errors return as the tool_result, so the model fixes its own spec.
  • The conversational brain (internal/bot/brain.go) β€” an executor, like create_app: when the data is already in context, the model can create a view directly.

Either way the sidecar β€” never the model β€” appends the open_view chip to the reply, labeled with the view's real title, so the chip can't claim one view and open another. open_view joins the client chip whitelist (ui.js CHIP_TYPES) at the harmless-and-reversible bar: the tap only opens a panel over state already folded on this device.

Reuse: the news-bot shape

A charter bot posts its morning summary, creates a view of the articles, and the reply carries Open view β€” headlines listed, each tapping through to the article. Same rail, no new machinery. /views (on the views app) reopens any live view in the thread later, so the chip isn't the only door.

Scope

Web-first, deliberately: native renders through the webview surfaces today, and the schema expansion is pure + twin-able (docs/app-schema.md Β§ tap.url notes the one additive primitive native still ignores). Fold vectors live at test/event-vectors/views.json so the native fold lands against the same spec. Interactive views (rows that emit events back to the thread) are the natural next rung β€” the schema engine already supports actions; the spec just doesn't expose them yet.