Something went wrong. Try again.
DASL — Data-Addressed Structures & Links — is a small set of simple, standard primitives for working with content-addressed, linked data. If
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410<!DOCTYPE html><html lang="en"><head>
<!--
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! GENERATED SPEC — DO NOT EDIT. Look for the .src.html instead !!!!!!!!! !!!!!!!!! HEY YOU !!!!!!!!! YES ***YOU****! !!!!!!!!! !!!!!!!!! You're about to edit a generated document. !!!!!!!!! Don't do that! Why would you do that. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-->
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Web Tiles</title> <link rel="stylesheet" href="spec.css"><link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect x=%220%22 y=%220%22 width=%22100%22 height=%22100%22 fill=%22%2300ff75%22></rect></svg>"><link rel="alternate" type="text/markdown" href="https://dasl.ing/tiles.md"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" property="og:title" content="DASL: Web Tiles"><meta name="twitter:description" property="og:description" content="Tiles are composable web docs and apps that can safely be used in arbitrary contexts and assembled from multiple sources to carry out complex user interface tasks. Their composability, security, and privacy properties make them ideal for use in social media, chat, agents, and malleable software."><meta name="twitter:image" property="og:image" content="https://dasl.ing/tiles.png"><meta name="twitter:image:alt" content="Very colourful stripes, so colourful it hurts"><meta name="twitter:url" property="og:url" content="https://dasl.ing/"><meta property="og:site_name" content="DASL"><meta property="og:locale" content="en"><meta name="theme-color" content="#00ff75"><script src="https://politi.es/badge.js" defer="defer"></script></head> <body><div class="nav-back">A specification of the <a href="/">DASL Project</a>.</div><main><header><h1>Web Tiles</h1><table><tbody><tr><th>date</th><td>2026-08-20</td></tr><tr><th>status</th><td><polities-badge polity="dasl.ing" deliverable="tiles"></polities-badge></td></tr><tr><th>editors</th><td><a href="https://berjon.com/">Robin Berjon</a> <<a href="mailto:robin@berjon.com">robin@berjon.com</a>></td></tr><tr><th>issues</th><td><a href="https://tangled.org/dasl.ing/dasl.ing/issues">list</a>, <a href="https://tangled.org/dasl.ing/dasl.ing/issues/new">new</a></td></tr><tr><th>abstract</th><td><div id="abstract"> <p> Tiles are composable web docs and apps that can safely be used in arbitrary contexts and assembled from multiple sources to carry out complex user interface tasks. Their composability, security, and privacy properties make them ideal for use in social media, chat, agents, and malleable software. </p> </div></td></tr></tbody></table></header> <section> <h2>Introduction</h2> <p> The web is a powerful platform that makes it possible to create high-quality content and applications. It would be desirable to be able to use it for social media posts, so that they could feature arbitrary content, and to be able to compose multiple small web apps together by having them talk to one another. Unfortunately, the dominant web security model is a poor fit for such tasks because it makes it way too easy to exfiltrate data, and is generally a privacy nightmare. </p> <p> Tiles change this by offering an environment in which arbitrary web content can be executed, while protecting against the egress of data by simply locking down the tile to whatever content is pre-listed in its manifest. The embedding app or context mediates all communication. This makes it possible to safely embed tiles in social media, chats, LLM interactions, or to compose them with one another. </p> <p> While the lack of network interaction is limiting, tile execution contexts can then make tiles powerful all the same. For instance, consider a poll tile in a group chat app: when one participant votes, their tile instance posts the vote to a communication channel provided by the chat application. The chat app then broadcasts this vote to all other participants' tile instances, keeping the poll results synchronized. </p> </section> <section> <h2>Tile Manifests</h2> <p> A tile manifest is simply a <a href="masl.html">MASL document</a>, using the Bundle Mode. The manifest must have a <code>name</code> and a <code>resources</code> entry, and <code>resources</code> must include a <code>/</code> entry for the root. </p> <p> That's all that's needed. The mechanism for obtaining the manifest depends on the context that the tile is loaded from, as detailed in the following sections. </p> </section> <section> <h2>Tiles on AT Protocol</h2> <p> A key use case for tiles is publishing them on the <a href="https://atproto.com/">AT Protocol</a> ([<a href="#ref-at" class="ref">at</a>]). Publishing a tile on AT is conceptually simple: every entry in <code>resources</code> must get uploaded as a blob to the relevant PDS and then the tile itself must be posted to the account's repository as a record containing the manifest. </p> <p> The details of the record and the MASL variant used on AT are specified in <a href="#lexicon">the relevant appendix</a>. Please pay particular attention to the details provided there about the manner in which <code>resources</code> need to point to blobs in order to operate correctly in an AT context. </p> </section> <section> <h2>Tiles in CAR</h2> <p> Tiles can be stored as files by packing them into <a href="car.html">CARballs</a> ([<a href="#ref-car" class="ref">car</a>]). All the resources must be in the CAR, and the CAR header must be the <a href="masl.html">MASL</a> metadata, along with the <code>roots</code> and <code>version</code> fields that CAR requires ([<a href="#ref-masl" class="ref">masl</a>]). </p> <p> As a convention, the tile carball may be named with a <code>.tile</code> extension. No particular media type is recommended at this time, the preferred way of publishing tiles online being over AT. </p> </section> <section> <h2>Tile Execution Contexts</h2> <p> In order to guarantee that tiles run in as safe and private a manner as possible, the web context into which they are loaded must meet restrictions that correspond to the following headers: </p> <pre><code data-highlighted="yes" class="hljs language-javascript">content-security-<span class="hljs-attr">policy</span>: <span class="hljs-keyword">default</span>-src <span class="hljs-string">'self'</span> <span class="hljs-attr">blob</span>: <span class="hljs-attr">data</span>:; script-src <span class="hljs-string">'self'</span> <span class="hljs-attr">blob</span>: <span class="hljs-attr">data</span>: <span class="hljs-string">'unsafe-inline'</span> <span class="hljs-string">'wasm-unsafe-eval'</span>; script-src-attr <span class="hljs-string">'none'</span>; style-src <span class="hljs-string">'self'</span> <span class="hljs-attr">blob</span>: <span class="hljs-attr">data</span>: <span class="hljs-string">'unsafe-inline'</span>; form-src <span class="hljs-string">'self'</span>; manifest-src <span class="hljs-string">'none'</span>; object-src <span class="hljs-string">'none'</span>; base-uri <span class="hljs-string">'none'</span>; sandbox allow-downloads allow-forms allow-modals allow-popups allow-popups-to-<span class="hljs-built_in">escape</span>-sandbox allow-same-origin allow-scriptscross-origin-opener-<span class="hljs-attr">policy</span>: same-origincross-origin-resource-<span class="hljs-attr">policy</span>: cross-originorigin-agent-<span class="hljs-attr">cluster</span>: ?<span class="hljs-number">1</span>permissions-<span class="hljs-attr">policy</span>: interest-cohort=(), browsing-topics=()referrer-<span class="hljs-attr">policy</span>: no-referrerx-content-type-<span class="hljs-attr">options</span>: nosniffx-dns-prefetch-<span class="hljs-attr">control</span>: off </code></pre> <p> In the current state of browser technology, loading tiles in a web context requires setting up an origin that is unique to the tile (currently it is random, though it may become derived from its CID), which in turn requires touching the network. It is recommended to use a server that is trusted by the user for this purpose (i.e. one that cannot learn more than what the embedding context already knows). </p> <p> Because the lack of network access is restrictive, tiles can be granted additional contextual access. A future version of this specification will add support for chat channels (to sync between tile instances in a chat) and an intent-based way for tiles to call one another. </p> </section> <section id="decisions"> <h2>Appendix: Decision Tree</h2> <p> The design of web tiles can be surprising to people who are used to typical apps. A core part of the model is that, while they can have access to relatively powerful capabilities, they are completely cut off from network access beyond loading their own pre-declared dependencies (and barring gating access through specific user action). </p> <p> This can feel restrictive, but two things mitigate those restrictions. First, depending on context, tiles can be granted access to functions that allow them to perform things that regular pages cannot, such as communicating with other instances of the same tile in a chat or invoking other tiles. And second — and most importantly — this restriction makes new applications possible. In many cases it's simply inconceivable to run a full-fledged web frame simply because it could exfiltrate too much sensitive data too easily. But since tiles have very constrained exfiltration paths, they are safe for use in private environments. </p> <p> To better understand this, we've made a decision tree that maps the design space for embedded apps and docs. You'll notice the tree omits whether users should be prompted for access to powerful capabilities as a load-bearing component of access to anything dangerous, including exfiltration. This is intentional: relying on users to correctly evaluate permission requests for powerful capabilities has repeatedly failed in practice. </p> <dl> <dt>Do you want to build atop web technology?</dt> <dd> <u>No</u>. That’s respectable, good luck to you. You’ll probably have make similar trade-offs from the rest of the tree, but they’ll depend a lot on the architectural fundamentals that you select and so it’s hard to discuss that in meaningful detail. You exit the tree here. </dd> <dd> <u>Yes</u>. Reuse is good. Painful, but good. <dl> <dt>What networking security model do you want?</dt> <dd> <u>At least partly arbitrary network access</u>. This may be somewhat restricted (SOP, CORS, etc.) but you can touch the network, which means that you can exfiltrate sensitive information from powerful APIs and generally violate privacy. <dl> <dt>Is installation gated to verify some degree of safety of the content?</dt> <dd> <u>No</u>. This is basically the web as supported by today’s browsers. You can change some things about how it’s implemented, what the UX is, but in general you will need to limit access to many powerful features, you will need to be careful to limit the loading of content not initially triggered by user action, etc. Nothing new here. </dd> <dd> <u>Yes</u>. This is an app store. We know where it ends. </dd> </dl> </dd> <dd> <u>No network access beyond pre-determined content</u>. This makes it possible to expose much more powerful APIs and much more private information, up to and including granting access for instance to a full private chat. The lack of access to the network is limiting, but it also renders the system composable and predictable. <dl> <dt>What kind of packaging tech do you use?</dt> <dd> <u>Zip-like</u>. This is essentially WebXDC. It’s a fine approach and has seen variants implemented. The downside is that loading content becomes tied to loading the whole bundle or playing challenging games with built-in Zip manifests and range requests. It makes including large content (e.g. parquet, video) difficult and in the general case it will murder the latency of your first paint and interactivity. </dd> <dd> <u>Manifest-like</u>. The limits to content loading are not defined by a packaging format but by a manifest that will map the bundle’s internal paths to loading mechanisms, ideally content addressed. The full content can be bundled if desired (e.g. into a CAR) but it can also be loaded through content-addressing indirection mechanisms, for instance from a PDS over AT. This is web tiles. </dd> </dl> </dd> </dl> </dd> </dl> </section> <section id="lexicon"> <h2>Appendix: Tiles Lexicon</h2> <p> Note that lexica are not flexible enough to be able to describe the <code>resources</code> map. Importantly, whereas regular MASL uses a CID to reference content, when in an AT record it does not use the <code>cid</code> type but rather the <code>blob</code> type ([<a href="#ref-masl" class="ref">masl</a>], [<a href="#ref-cid" class="ref">cid</a>], [<a href="#ref-at" class="ref">at</a>]). This is because a PDS will not pay particular attention to a CID but will need an uploaded blob to be referenced by a blob type in a record. </p> <p> This has important implications with how MASL needs to be used over AT in order to work correctly with deployed PDSs. An example <code>resources</code> entry for a root HTML document looks as follows: </p> <pre><code data-highlighted="yes" class="hljs language-javascript">{ … <span class="hljs-string">"resources"</span>: { <span class="hljs-string">"/"</span>: { <span class="hljs-string">"src"</span>: { <span class="hljs-string">"$type"</span>: <span class="hljs-string">"blob"</span>, <span class="hljs-string">"ref"</span>: { <span class="hljs-string">"$link"</span>:<span class="hljs-string">"bafkreicknles2uzv7sruakcbjtn5pnoc4rqmgkvnavuq4raxexjvcn7osq"</span> }, <span class="hljs-string">"size"</span>: <span class="hljs-number">20</span>, <span class="hljs-string">"mimeType"</span>: <span class="hljs-string">"application/octet-stream"</span> }, <span class="hljs-string">"content-type"</span>: <span class="hljs-string">"text/html"</span> }, … }} </code></pre> <p> A few things are worth noting in the above: </p> <ul> <li>The <code>$type</code> is required.</li> <li>The <code>ref</code> is what's used to link to the CID.</li> <li>The <code>size</code> is required by the PDS and it must be correct (in bytes).</li> <li> The <code>mimeType</code> is required by at least some PDS versions and implementations. Note that it doesn't need to match the <em>actual</em> <code>content-type</code> that is provided separately. In fact, in some PDS versions it <em>cannot</em> match it in many cases. It is recommended, wherever possible, to just use <code>application/octet-stream</code> there. </li> </ul> <p> The full lexicon is: </p> <pre><code data-highlighted="yes" class="hljs language-javascript">{ <span class="hljs-string">"id"</span>: <span class="hljs-string">"ing.dasl.masl"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Lexicon for DASL (https://dasl.ing/) types used on AT, notably for Web Tiles."</span>, <span class="hljs-string">"defs"</span>: { <span class="hljs-string">"masl"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"MASL metadata as defined in https://dasl.ing/masl.html"</span>, <span class="hljs-string">"properties"</span>: { <span class="hljs-string">"name"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"The name for the tile, can be a title or app name"</span>, <span class="hljs-string">"maxLength"</span>: <span class="hljs-number">1000</span>, <span class="hljs-string">"maxGraphemes"</span>: <span class="hljs-number">100</span> }, <span class="hljs-string">"description"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Short overview of the content"</span>, <span class="hljs-string">"maxLength"</span>: <span class="hljs-number">3000</span>, <span class="hljs-string">"maxGraphemes"</span>: <span class="hljs-number">300</span> }, <span class="hljs-string">"categories"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"array"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Tags categorising the tile"</span>, <span class="hljs-string">"items"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span> } }, <span class="hljs-string">"background_color"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"A colour for the background of the tile"</span> }, <span class="hljs-string">"icons"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"array"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Icons for the tile"</span>, <span class="hljs-string">"items"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>, <span class="hljs-string">"required"</span>: [<span class="hljs-string">"src"</span>], <span class="hljs-string">"properties"</span>: { <span class="hljs-string">"src"</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span> }, <span class="hljs-comment">// has to be in resources</span> <span class="hljs-string">"sizes"</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span> }, <span class="hljs-string">"purpose"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span> } } } }, <span class="hljs-string">"screenshots"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"array"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Screenshots, can be used for banner or card images"</span>, <span class="hljs-string">"items"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>, <span class="hljs-string">"required"</span>: [<span class="hljs-string">"src"</span>], <span class="hljs-string">"properties"</span>: { <span class="hljs-string">"src"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span> }, <span class="hljs-string">"sizes"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span> }, <span class="hljs-string">"label"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span> } } } }, <span class="hljs-string">"sizing"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Requesting sizing properties for the content"</span>, <span class="hljs-string">"properties"</span>: { <span class="hljs-string">"width"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"integer"</span>, <span class="hljs-string">"mininum"</span>: <span class="hljs-number">1</span> }, <span class="hljs-string">"height"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"integer"</span>, <span class="hljs-string">"mininum"</span>: <span class="hljs-number">1</span> } }, <span class="hljs-string">"required"</span>: [<span class="hljs-string">"width"</span>, <span class="hljs-string">"height"</span>] }, <span class="hljs-string">"resources"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"unknown"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"A mapping of path to object with a CID src and HTTP headers"</span> }, <span class="hljs-string">"short_name"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"A name, in case the basic name cannot fit"</span> }, <span class="hljs-string">"theme_color"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Theme colour"</span> }, <span class="hljs-string">"prev"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"cid-link"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"In case there are multiple versions of this tile, this is the CID of the previous one"</span> }, <span class="hljs-comment">// CAR compatibility</span> <span class="hljs-string">"version"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"integer"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"The CAR version — avoid using this"</span>, <span class="hljs-string">"const"</span>: <span class="hljs-number">1</span> }, <span class="hljs-string">"roots"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"array"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"The CAR roots — avoid using this"</span>, <span class="hljs-string">"items"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"cid-link"</span> } }, }, <span class="hljs-string">"required"</span>: [<span class="hljs-string">"name"</span>, <span class="hljs-string">"resources"</span>] }, <span class="hljs-string">"main"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"record"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"A tile, instantiating MASL metadata into a record"</span>, <span class="hljs-string">"key"</span>: <span class="hljs-string">"tid"</span>, <span class="hljs-string">"record"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>, <span class="hljs-string">"required"</span>: [<span class="hljs-string">"cid"</span>, <span class="hljs-string">"tile"</span>, <span class="hljs-string">"createdAt"</span>], <span class="hljs-string">"properties"</span>: { <span class="hljs-string">"cid"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"The DRISL CID of the MASL for the tile"</span>, <span class="hljs-string">"format"</span>: <span class="hljs-string">"cid"</span> }, <span class="hljs-string">"tile"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"ref"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"The MASL content"</span>, <span class="hljs-string">"ref"</span>: <span class="hljs-string">"ing.dasl.masl#masl"</span> }, <span class="hljs-string">"createdAt"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-string">"description"</span>: <span class="hljs-string">"Timestamp"</span>, <span class="hljs-string">"format"</span>: <span class="hljs-string">"datetime"</span> } } } } }} </code></pre> </section>
<section><h2>References</h2><dl><dt id="ref-at">[at]</dt><dd><a href="https://atproto.com/specs/atp"><cite>AT Protocol</cite></a>. URL: <a href="https://atproto.com/">https://atproto.com/</a></dd><dt id="ref-car">[car]</dt><dd>Robin Berjon & Juan Caballero. <a href="https://dasl.ing/car.html"><cite>Content-Addressable aRchives (CAR)</cite></a>. 2026-08-20. URL: <a href="https://dasl.ing/car.html">https://dasl.ing/car.html</a></dd><dt id="ref-cid">[cid]</dt><dd>Robin Berjon & Juan Caballero. <a href="https://dasl.ing/cid.html"><cite>Content IDs (CIDs)</cite></a>. 2026-08-20. URL: <a href="https://dasl.ing/cid.html">https://dasl.ing/cid.html</a></dd><dt id="ref-masl">[masl]</dt><dd>Robin Berjon & Juan Caballero. <a href="https://dasl.ing/masl.html"><cite>MASL — Metadata for Arbitrary Structures & Links</cite></a>. 2026-08-20. URL: <a href="https://dasl.ing/masl.html">https://dasl.ing/masl.html</a></dd></dl></section></main></body></html>