- php-mf2 for parsing microformats2 data
- also php-mf2-shim for getting mf2 data out of silos
- taproot/indieauth a PSR-7-compatible IndieAuth server library
- taproot/micropub-adapter a PSR-7-compatible micropub adapter library
- taproot/subscriptions for easily subscribing+crawling any web content
- taproot/archive your personal opinionated indieweb/microformats-oriented HTML archiver
- indieweb/original-post-discovery for determining the canonical URL from POSSE copies
- mf-cleaner for handling microformats2 array structures better
- diaspora-export for exporting public posts from Diaspora
- php-abc for parsing headers out of ABC notation
php-helpers: static helper functions, some utility classes and POSSE stuff including the truncenator. Slowly having the useful parts extracted into smaller packages.
librarianfor DB indexing of flat files, no longer used/maintained
php-activitystreamsactivitystreams implementation, no longer used/maintained
taproot/authentication library concisely implementing indieauth client app + resource server logicreplaced by taproot/indieauth.
Most of the Taproot design artifacts are up on github too in taproot/design.
All content-creation UIs in taproot are publicly viewable — take a peek!
- 1 Design Principles
- 2 IndieWeb Support
- 3 Itches and Roadmap
- 4 WIP Design notes for future development
- 5 History and Development
- 6 See Also
In addition to the indiewebcamp principles, these are some principles I have discovered through building taproot.
- Build software which feels safe, all other concerns (privacy, security, technically exciting) are secondary  — this is based on personal experience through selfdogfooding taproot
- Easy things should be easy, remove friction so hard things are only has hard as they need to be 
- Use event listener patterns to build upon the same data in intelligent ways 
- Build reusable, framework-agnostic components and release them to the public, not monolithic products.
Taproot was named on .
Taproot supports the following:
- All content is marked up using microformats2 so parse at your leisure.
- Supports posting of notes, articles and music — other post types are faked with HTML inside notes
- Sends pingbacks and webmentions to all the links on every post
- Receives webmentions and pingacks and displays them as comments, likes, reposts or mentions
- Hooked up to Bridgy to display silo interactions in the same place
- reply notes parse the URL they’re in-reply-to and display a reply-context if microformats are present
- Notes have webactions with twitter intent fallback if POSSEd to twitter
- ATOM fallback is provided for all feeds
- Notes and articles are POSSEd to twitter automatically and sometimes manually/indirectly to facebook
- Logging in using indieauth and distributed-indieauth
- New post UI is publicly viewable and can be used to create new posts on other people’s micropub-enabled sites
Itches and Roadmap
Currently working on:
- Using Taproot every day to post content
- Getting up to speed with all the new stuff from the several years I’ve been idle, implementing it in Taproot
- Adding better micropub support via taproot/micropub-adapter
- Moving webhost, upgrading to latest PHP
Next up, in approximate order of priority:
- Refactor app, replace silex with a modern mini web framework based on PSR-7, e.g. Slim
- Replace DIY CSV indexes with sqlite indexing
- Re-organise content storage and presentation to make it easier to create feeds with multiple content types in (e.g. homepage feed, tag page feeds)
- Make it possible to post a wider variety of responses to content, hidden from the homepage feed by default
WIP Design notes for future development
Content Storage and Versioning
Store multiple versions of post content and drafts e.g.
/data/articles/article-slug/… current-content.html <- The latest published content, lives at permalink current-draft.html <- The latest unpublished draft 2021-05-12T100000-content.html <- An update made without publishing 2021-05-10T123000-content.html <- An updated draft which was published 2021-05-10T120000-draft.html <- An unpublished draft version 2021-05-08T110000-content.html <- The original article, published as-is
Store both the user-entered text (typically HTML + enhancement e.g. markdown, auto-linking, auto-embedding etc.) and the rendered HTML for speed. Potentially allow the user to “freeze” the rendered HTML into the user-editable field — which means that enhancement must be idempotent! Alternatively, allow the user to freeze the rendered HTML and turn off enhancements. Sounds complicated but it should be easy enough with revisions and live preview.
Versions and Drafts
(To consider: do we index each revision in the main database? probably not) (To consider: do we have a “current” file, or just name everything by the datetime and choose the latest one? Do we include draft/published information in the filename, or only in the files themselves?)
Each one contains something like an _update dict which lists the datetime updated, client ID of the app making the update, optional edit notes.
Logged-in user navigating to /articles/article-slug/ sees the current published by default. As there’s an unpublished draft which is more recent than the published version, they get a banner indicating that there’s an outstanding draft. Entering the editing UI shows the most current draft by default, and a “previous versions” list. The user can navigate easily between versions, and a live preview updates as they do, showing how each one looks.
User can delete previous revisions. Drafts are always private, previous versions of the article are either private or public depending on user setting, optionally overridden on a case-by-case basis. Revision URLs look like /articles/article-slug/versions/YYYY-MM-DDTHHMMSS
When content moves, its revision history is moved to the new ID location, and the old ID looks like this:
/data/articles/old-article-slug/… current-content.html or redirect.txt <- Contains a redirect directive to /article-slug/
Current /article-slug contains a list of all resolved URL paths (e.g. '/articles/article-slug/', '/articles/old-article-slug/'), all of which are queried against when searching for mentions of the current article, so as not to lose connections to old responses when moving content.
(Another approach would be to make an internal GUID which moves with the content when its URL changes. Consider pros/cons of this approach.)
Collections, Feeds and Storage
Every piece of content belongs to a collection, which determines the structure of its permalink (e.g. /articles/ vs /notes/), where it’s stored on disk, how it’s retrieved and stored, and the default templates used to display, create and edit it.
All timestamped content is indexed in the same main index, and can have the same queries run over it (see Indexing).
Additionally, it’s possible to create feed views over a query, which will be available at a URL (e.g. homepage feed at /, tag feeds at /tags/indieweb) and which display multiple content types.
To consider: Possibly also possible to create standalone pages?
All timestamped content is indexed together in a table, SQLite by default. This index is always disposable and can easily (and relatively quickly) be regenerated from the data on disk. It should have as many columns with as much potentially useful and queryable information in as possible, to make interesting queries possible.
Personal Data Management
Have a console command and/or web UI for easy GDPR compliance. Allows the site owner to easily search their site for personal information, showing where it’s located and giving shortcuts to edit it out/delete it.
Micropub Integration and Scopes
As well as general update, delete and read scopes, offer corresponding scopes which apply only to content which that particular client has created, to give a client full control over content it created, but limit the “damage” it could do elsewhere.
Maybe also have a create_draft scope, which treats a “create” action as creating a new draft, which the user then has to review in Taproot’s UI to publish.
Update actions create new revisions, so everything is reversible.
Update actions which would change the ID/URL of the post move it as detailed above.
Delete actions create a new revision with a deleted flag set. Undelete actions probably just delete the latest revision file, causing the previous non-deleted one to become active again. Undeletes should never cause the URL to change (too complicated and annoying).
History and Development
Done (latest first):
- 2021-05 Improved styling, fixed bugs in webmention endpoint, updated homepage bio with tag links
- Implemented anti-DDOS measures (tried client validation, ditched and went with expiring webmention endpoints)
- Publishing venues with reviews, inspired by Tom Morris
- Implemented a basic micropub endpoint, capable of creating notes
- Made it easier to edit note and article published datetimes and lists of syndication URLs
- Timezones now included in all datetimes in feeds and posts
- Showing mentions+mention counts on articles as well as notes, made templates and logic more reusable
- Composite feed on homepage instead of just notes
- Implemented distributed Indieauth and micropub, allowing people to use my new-note UI to create posts on their own site
- Integrated with Bridgy instead of manually shimming backfeed
- Added file upload control to note uploading UI
- Improved new note location UI with interactive map preview for manual adjustments
- Removed dedicated ATOM endpoints, replaced with generic mf2 to ATOM conversion endpoint
- Basic phone UI on homepage
- works nicely on desktop, needs improvement and Tropo involvement for good mobile UX
- Mobile-oriented homepage design as per previous brainstorming
- Allowed notes to have names, which display as a small title at the top of the note. For shorter named articles which don’t quite fit into /articles, which is styled for long-form “serious” essays
- Improved check in/location note UI, location now shown on interactive Leaflet map before posting, can be corrected by dragging on map
- Added last-seen location+map, last 3 articles to homepage
- Added a homepage feed after feed reader research demonstrated need for URL -> latest posts
- Deploying new rewritten version
- Rewriting the whole thing to remove frameworkey nonsense code
- using silex as app library
- no DB — faking it with CSV indexes like the idiot I am (it’s actually surprisingly effective)
- Accepting webmentions as well as pingbacks
- Federated comments
- Major migration away from DB storage, toward flat files, with lots of codebase cleanup in the process
- Import of old posts from Diaspora see them here
- Frontend js using Backbone and requirejs
Some old screencaps from around 2013: