Tabs & Spaces Developers

Build

Reading the workspace

Besides its own data, your app can read what's already in the workspace: its spaces, people, agents, tasks and files (and, if it asks, what's in them). That's how a tab draws a pie chart of a space's tasks, or a home page shows how many people and agents a workspace has.

Two rules decide what it sees:

  • Only what it asks for. Your manifest lists permissions. An owner or admin sees them before installing your app, and gives them by installing it.
  • Only what the person looking can see. A tab shown to Ada reads what Ada could read herself: never a private space she isn't in. A guest's view holds only their spaces and the people in them.

Reads only read. They work while a workspace is read-only, and nothing they return is kept by the workspace for your app.

Asking for permissions

json
{
  "manifestVersion": 1,
  "id": "acme-dashboard",
  "name": "Dashboard",
  "tabs": [{ "id": "board", "name": "Board", "url": "/tab" }],
  "permissions": ["tasks.read", "workspace.read"]
}
PermissionReads
workspace.readThe workspace's name, and how many people, guests, agents and spaces it has
spaces.readSpaces: names, descriptions, visibility and how many people are in each
members.readPeople: names, roles, job titles and when they joined. Never email addresses.
agents.readAgents: names, titles and descriptions
tasks.readTasks: titles, statuses, assignees, due dates
files.readFiles' details: names, folders, types, sizes, who changed them. Not contents.
files.contentWhat's in a file, by its id

Ask only for what your app needs: admins see the list, in words, before they install it, and so does everyone on your marketplace listing. A permission a workspace doesn't know (from a later version) makes your manifest fail to install there.

Asking for more later: when an update adds a permission, Check for updates doesn't take it at once. The admin is shown what's new and updates with a yes. Until then your app keeps its old manifest, and a read that needs the new permission fails with PERMISSION_NOT_GIVEN.

Reading, from your page

connect() gives your page read:

js
import { connect } from "@tabs-and-spaces/sdk";

const tands = await connect();

// A pie chart of this space's tasks.
const { total, groups } = await tands.read.countTasks();
// groups: [{ key: "todo", name: "todo", count: 3 }, { key: "in_progress", … }, …]

// The workspace at a glance (on a home page, say).
const workspace = await tands.read.workspace();
console.log(workspace.counts); // { members: 12, guests: 2, agents: 4, spaces: 9 }
MethodNeedsReturns
workspace()workspace.read{ id, slug, name, counts: { members, guests, agents, spaces } }
spaces(page?)spaces.read{ spaces, cursor }: each { id, kind, name, description, visibility, memberCount, joined, createdAt }
members(page?)members.read{ members, cursor }: each { id, name, role, title, joinedAt }
agents()agents.read[{ id, name, title, description, builtIn }]
tasks(query?)tasks.read{ tasks, cursor }, latest change first: each { id, spaceId, title, status, assignees, dueAt, createdAt, updatedAt, completedAt }
countTasks(query?)tasks.read{ total, groups: [{ key, name, count }] }
files(query?)files.read{ files, cursor }, latest change first: each { id, spaceId, name, folder, contentType, size, updatedAt, updatedBy }
fileContent(id)files.content{ file, encoding, content }: the file's details, and what's in it

Counts the person can see: workspace() counts only the spaces they can read, and a guest only the people in their spaces. Spaces are General, the person's own My Space and the other spaces they can read; direct messages aren't listed.

Where it looks

Tasks and files are read in one place, or several:

OptionLooks in
(nothing)Where your page is: your tab's space. A home page: the whole workspace.
within: "space"Your tab's space (a home page has none: NO_SPACE)
within: "workspace"Every space the person can read
spaceId: "…"One of those, by id (from spaces(); one they can't read is NOT_FOUND)

A space's own tab with no server reads tasks and files in its space only: within: "workspace" is that space, and any other spaceId is NOT_FOUND.

Tasks

tasks() and countTasks() also take:

  • status: the statuses to include, from todo, in_progress, waiting, done and cancelled.
  • assignee: a person's or agent's id, or "me" for the person looking.

countTasks({ by }) counts by status (the default: every status, 0 where there are none, in that order), by assignee ("" is nobody's; a task with two assignees counts for each) or by space, most first, with each group's name:

js
const mine = await tands.read.tasks({ assignee: "me", status: ["todo", "in_progress"] });
const bySpace = await tands.read.countTasks({ within: "workspace", by: "space" });

Files

files({ query }) finds files whose names or contents have the words, as the workspace's search does. It gives a file's details, never its contents.

fileContent(id) reads what's in one, with files.content: a file from files(), or one the person chose. It looks wherever the person looking can read, up to 5 MB (a bigger file is TOO_LARGE). Text files (text/*, JSON, XML, YAML, SVG) come as they are, with encoding: "text"; anything else in base64:

js
const { files } = await tands.read.files({ query: "brief" });
const { file, encoding, content } = await tands.read.fileContent(files[0].id);

if (encoding === "text") show(content);
else image.src = `data:${file.contentType};base64,${content}`;

Ask for files.content only if your app needs what's in files: admins see it before installing, as "What's in files".

Pages

Lists come a page at a time: up to limit (1–200, default 100), with a cursor for the next page (null at the end):

js
let cursor;
do {
  const page = await tands.read.members({ cursor });
  show(page.members);
  cursor = page.cursor ?? undefined;
} while (cursor);

Reading, from your server

An action's data key reads too, for the person who asked and the action's space:

js
import { dataClient } from "@tabs-and-spaces/sdk/server";

const data = dataClient(request);
const { groups } = await data.read.countTasks({ within: "workspace" });

Refusals

CodeWhen
PERMISSION_NOT_GIVENYour app wasn't given the permission this read needs
NOT_FOUNDspaceId isn't a space the person can read, or id a file they can read
TOO_LARGEfileContent of a file over 5 MB
NO_SPACEwithin: "space" from a home page
INVALIDA cursor that isn't one the workspace gave

Trying it out

tands dev answers reads from a made-up workspace: four people (one a guest), four spaces, two agents, ten tasks and a few files, the space you're looking from among them. It checks your manifest's permissions as a workspace does: add one and reload.

Search every page: actions, the manifest, the SDK, tokens…