Build
Tabs without a server
A tab needn't have a server of its own. Give a workspace a folder of HTML, CSS and JavaScript with a tands.json at the top, and it keeps the files in its own storage and shows them in a sandboxed frame. It's an app like any other: the same manifest, the same SDK, its own data, and what it reads of the workspace.
That's the quickest way to a chart of a space's tasks, or a page that shows a workspace at a glance. You can make one without leaving the workspace, from a template, and edit it there; or build it on your computer and upload it.
Make one in the workspace
Add tab → Custom tab → Make your own (anyone who manages the space's tabs), or Settings → Apps → New app (owners and admins), offers templates:
| Template | What it is | Reads |
|---|---|---|
| Tasks by status | A donut of the space's tasks: to do, in progress, waiting and done | tasks.read |
| Recent files | The space's files, latest change first, with who changed them | files.read |
| Workspace at a glance | The workspace in numbers, and its spaces, as a tab and a home page (Settings only) | workspace.read, spaces.read |
| Blank | A page that says hello to whoever opens it | nothing |
Give it a name, check what it adds and reads, and it's made: its tab opens with the editor beside it.
Asking an agent
In a space's conversation, ask an agent for a tab ("make a tab with a donut of our tasks by status"). It writes the files and shows them as a card under its answer: its files, what it reads, and Preview, the page as you'd see it here, reading what you can see and keeping nothing. Add the tab keeps it as the space's own app and adds its tab; nothing is kept before that. Only the person who asked decides, and only if they may add tabs to the space.
Ask it to change one the same way ("make the chart's heading say Launch"): it reads the tab's files, and its new version is shown and approved the same way, with anything it now reads marked. Agents write text files (HTML, CSS, JavaScript, JSON, SVG); a tab with pictures or fonts is changed by uploading.
Editing it
A tab or home page whose files the workspace keeps has Edit under it, for whoever may upload it. The editor opens beside the page: its files on the left, the one you're editing in the middle, the page on the right (on a narrow screen, Code and Preview switch between them).
- Save (or ⌘/Ctrl + S) makes a new version, as an upload does, and the page reloads with it. A version that asks to read more is shown first, to agree to.
- New file adds an HTML, CSS, JavaScript, JSON, text or SVG file. Pictures and fonts can't be edited here: upload a new version with them.
- Tab indents; Esc then Tab moves on from the editor.
From Settings → Apps → your app → Edit files, the editor opens on its own, without the page.
Start one on your computer
npx tands new tasks-chart --tab
npx tands dev tasks-chart--tab makes a tab with no server from the same templates (--starter tasks-by-status, the default; space-files, workspace-glance or blank), with the SDK copied in. tands dev with a folder serves its files as a workspace does, and opens the harness at http://localhost:5470, with made-up tasks to read.
A bundle is a folder like this:
tasks-chart/
tands.json the manifest
index.html a tab's page
app.js
style.css
tands-sdk.js the SDK, built (or bundle @tabs-and-spaces/sdk yourself){
"manifestVersion": 1,
"id": "tasks-chart",
"name": "Tasks by status",
"tabs": [{ "id": "tasks", "name": "Tasks by status", "url": "index.html" }],
"permissions": ["tasks.read"]
}Pages and the icon are paths in the folder. Use relative paths between your files (app.js, ./img/logo.png): the workspace serves them from an address of its own.
Put it in a workspace
- In a space: Add tab → Custom tab → Make your own, then choose the folder or a .zip of it. Anyone who manages the space's tabs can. It's that space's own: its tab can be added there, and only there.
- For every space: an owner or admin chooses Settings → Apps → New app. A workspace's app can also offer home pages.
Files without a tands.json get one: you're asked for a name and which page the tab shows.
Either way, what it adds and what it reads are shown first, as when an app is installed. Uploading gives it the permissions it asks for; a new version that asks for more is shown again before it's kept.
In every space
A space's own app can become the workspace's, for every space. In Settings → Apps, the people who manage its space's tabs choose Ask to use in every space; the workspace's owners and admins are emailed, and see the request at the top of the list. They Approve it (seeing what it reads, which then reaches every space the person looking can see) or Decline it, with why; whoever asked is emailed either way. Owners and admins can also choose Use in every space without being asked.
Once it's the workspace's, every space can add its tabs, its tabs in its first space keep working, and its owners and admins edit and update it (the push keys of anyone else stop working). It can offer home pages from then on.
Update it
Edit it in the workspace (above), Settings → Apps → your app → Upload a new version, or from your computer or CI:
TANDS_URL=https://your.workspace.example TANDS_PUSH_KEY=tas-pk-… npx tands push tasks-chartMake the key in Settings → Apps → your app → Push keys (owners and admins, and for a space's own app, the people who manage its tabs). A key uploads that app's new versions, as whoever made it, while they still may; it can't publish, or do anything else. tands push checks the folder first, and uploads nothing when the files haven't changed. A version that reads more than the app was given isn't taken from a key: upload that one in the workspace, where someone can agree to it.
Each upload is a new version. The one before is gone once it's in: tabs open the new one when they're next opened.
What's different
The workspace serves your files to a frame that has no origin of its own and no network:
| You can | You can't |
|---|---|
| Run scripts, inline or from your files, and use modules | fetch, XMLHttpRequest, WebSockets or EventSource |
| Load your own styles, pictures, fonts, sounds and videos | Load scripts, styles, pictures or fonts from anywhere else |
| Read and keep data, read the workspace, report events | Keep things in localStorage, sessionStorage, IndexedDB or cookies (use values instead) |
| Open links in a new tab, and have records actions agents can use | Have actions with a url: they need a server (a bundle with one is refused) |
Everything your page needs comes through the SDK, from the workspace. The token your page is told is still signed, but no server checks it.
Share it as a template
Publish it to the marketplace from Settings → Apps → your app → Publish: it's listed as a tab template, or a home page template when it offers one. See Publishing a template and Adding a template.
Limits
| What | At most |
|---|---|
| A bundle | 10 MB |
| Files in a bundle | 300 |
| One file | 5 MB |
| The icon | 100 KB |
Files may be HTML, CSS, JavaScript, JSON, source maps, text, Markdown, CSV, SVG, PNG, JPEG, GIF, WebP, AVIF, icons, fonts (WOFF, WOFF2, TTF, OTF), MP3, MP4, WebM and WebAssembly. Hidden files (.git, .DS_Store) and node_modules are left out.
Refusals
| Code | When |
|---|---|
BUNDLE_NOT_A_ZIP | The upload isn't a .zip |
BUNDLE_NO_MANIFEST | There's no tands.json at the top (or one folder down) |
BUNDLE_INVALID | The manifest isn't valid, a page isn't an HTML file in it, a file isn't allowed, it has actions with a url, or a space's app offers home pages |
BUNDLE_TOO_LARGE | Over the limits above |
APP_ID_TAKEN | An app with its id is installed from an address, or is another space's own |
NEEDS_AN_ADMIN | A space's app asks to read beyond its space (workspace.read, spaces.read, members.read, agents.read): only an owner or admin can give that |
NEW_PERMISSIONS | It reads more than the upload gives it (always, from tands push: upload that version in the workspace) |
A space's own app reads tasks and files in its space only, whatever it asks for (within: "workspace" is that space too). Once it's in every space, it reads as any workspace's app does.