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
{
"manifestVersion": 1,
"id": "acme-dashboard",
"name": "Dashboard",
"tabs": [{ "id": "board", "name": "Board", "url": "/tab" }],
"permissions": ["tasks.read", "workspace.read"]
}| Permission | Reads |
|---|---|
workspace.read | The workspace's name, and how many people, guests, agents and spaces it has |
spaces.read | Spaces: names, descriptions, visibility and how many people are in each |
members.read | People: names, roles, job titles and when they joined. Never email addresses. |
agents.read | Agents: names, titles and descriptions |
tasks.read | Tasks: titles, statuses, assignees, due dates |
files.read | Files' details: names, folders, types, sizes, who changed them. Not contents. |
files.content | What'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:
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 }| Method | Needs | Returns |
|---|---|---|
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:
| Option | Looks 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, fromtodo,in_progress,waiting,doneandcancelled.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:
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:
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):
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:
import { dataClient } from "@tabs-and-spaces/sdk/server";
const data = dataClient(request);
const { groups } = await data.read.countTasks({ within: "workspace" });Refusals
| Code | When |
|---|---|
PERMISSION_NOT_GIVEN | Your app wasn't given the permission this read needs |
NOT_FOUND | spaceId isn't a space the person can read, or id a file they can read |
TOO_LARGE | fileContent of a file over 5 MB |
NO_SPACE | within: "space" from a home page |
INVALID | A 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.