The Breakroom

Tapestry Unwoven

February 6, 2024

By Craig Hockenberry

It’s a bold claim to say you can make a universal timeline for the Internet.

But you can. And we have.

This post will explain the technology behind Project Tapestry and how we tested it as a prototype. We’ll keep this discussion at a fairly basic level: if you’re a web or app developer, you’ll have no problems following along.

And if you think I’m going to describe RSS feeds now, think again! We’ve come up with something completely new.


The key to this project was to use the same technologies as modern web development. Namely, the JSON data format and JavaScript for processing.

That is not to say that Project Tapestry is a web app. It is fully native and implemented with UIKit and SwiftUI.

Our insight was to use the web technologies as a way to extend Tapestry when it talks to the Internet. These plug-ins are built with JavaScript that is executed as needed by the native app. JSON files are used for metadata and configuration with the app.

When you add a plug-in to Tapestry, you are creating a place where JavaScript can be executed. For example, there is only one plug-in for Mastodon, but each time you use that plug-in for an account, a JavaScript environment is created. This lets Tapestry support multiple accounts or use a single plug-in for multiple RSS feeds.

Our goal is to make an app that can gather any publicly available data: if it uses an IP address and HTTP, we feel confident that it can be woven into Tapestry’s timeline. If you have a Raspberry Pi in your garden that generates a CSV file with the current level of a water tank, it can be a part of your beautifully designed universal timeline.


Tapestry does not communicate directly with the web. Instead, it asks a plug-in to fetch data and place it into JavaScript objects. There are three types of objects in our prototype:

Post: A unique piece of content gathered from the Internet. Its uniqueness is guaranteed by a Uniform Resource Identifier (URI). It must also have a timestamp so it can be displayed chronologically. The content of the post is text with HTML formatting. The formatting is lenient: we’re big fans of Postel’s Law.

Creator: The entity that created the post. For a social media post, it’s a person, but for a satellite image it would be the name of the spacecraft. This object also uses a URI to guarantee uniqueness. (John Doe on Mastodon will be different creator than John Doe on Bluesky). Each Post object has a single creator.

Attachment: A piece of media that can be displayed with a post. The only requirement for an attachment is that it contains a URL to display. It can optionally have accessibility text and a thumbnail image. A Post can have multiple attachments.

These JavaScript objects are just used to transfer data. The app itself uses Swift objects to display the timeline and eventually persist the data.


This separation of data between an internal state (Swift) and an external state (JavaScript) has another important advantage:

The JavaScript code has no knowledge or access to login credentials or other data in the timeline.

Each plug-in calls a function named sendRequest which returns a JavaScript Promise. There is no Fetch API or XHR in the plug-in’s environment, so this is the only way the plug-in can access the Internet.

The sendRequest function is implemented by Tapestry and it adds any required authorization (either OAuth or JWT) before contacting the server. This makes it easier to develop plug-ins because the authorization dance is handled automatically as tokens are needed (including after expiration).

Data in the timeline is also protected by JavaScript’s strict sandbox. It’s just like one web page’s JavaScript not knowing about what’s going on in another page. The plug-ins only have enough information to perform their own tasks.

In Practice

JavaScript code turns out to be an excellent pre-processor for Tapestry’s universal timeline. It’s a natural environment to filter and massage JSON data coming from a URL. And by extending JavaScript’s built-in functions with a mechanism to convert XML to JSON, we can handle all modern structured data. (It’s how we handle Atom and RSS for blog feeds.)

To give you an idea of the effort involved: a plug-in to get recent earthquakes was implemented in about an hour. It was done with no prior knowledge of the API or data formats involved.

Supporting Bluesky took a bit longer: about a day including adding support for JSON Web Tokens (JWT).

There is a GitHub repository with full documentation of the JavaScript API and sample plug-ins. We think you’ll find that it’s a robust and extensible system, just like the web itself.

We’ve also created a developer tool called Tapestry Loom that lets you debug a plug-in using Safari’s Web Inspector. This Mac app makes it easy to inspect data coming from an endpoint and make sure the right objects are being created. You can download it, and experiment with the plug-ins, all from the project page.

Our long-term goal is to make these plug-ins user manageable. Something akin to installing Shortcuts on Apple devices. But while the plug-ins themselves may be easy to create, building a secure distribution system is not: it’s a stretch goal for our Kickstarter.

But Wait, There’s More!

Join me in a screencast that shows you all these pieces working together and expands on some of the concepts we’ve presented above. This 30 minute video will hopefully make our fellow developers as excited about Tapestry as we are!

So there you have it: the overall architecture of our universal timeline. With your support, we can make it a reality.