Angelo Gladding
lahacker.net

DLV5VBQ74B942A31

angelo@lahacker.net

South Pasadena, California, United States 67°F

Implementing the IndieWeb

from IPython.display import HTML
path = %env PATH
path = "/home/gaea/detritus/bin:" + path
%env PATH=$path

import random
import re
import time
env: PATH=/home/gaea/detritus/bin:/home/gaea/.nvm/versions/node/v14.6.0/bin:/home/gaea/understory/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

The IndieWeb at its core is a network of personal websites where individuals publish to their own and consume from their peers'. Interactions between sites are driven by a collection of incremental "building block" standards and protocols developed by a community of implementors. External tools and services leverage these building blocks to further facilitate the flow of interactions.

If you're looking for a pre-packaged and polished user experience see Canopy: A Full-Stack IndieWeb Client.

Getting Started

We will use the web library to create applications, spawn browsers and generate a network graph.

from web import *

Summaries will be pulled directly from the official wiki.

wiki = cache(domain="indieweb.org", db="wiki.db")

class quote_wiki:
    def __init__(self, page):
        self.url = f"//indieweb.org/{page}"
        def replace_link(matchobj):
            return f'<a href="{self.url}">{matchobj.groups(0)[0]}</a>'
        self.summary = re.sub(f"({page.replace('-', ' ')})", replace_link,
                              wiki[page].entry["summary"], count=1,
                              flags=re.IGNORECASE)
    def _repr_html_(self):
        return f"<p><q cite={self.url}>{self.summary}</q></p>"

Applications

alice_app = application("Alice", host="alice.example", icon="alice16.png",
                        sessions=True, serve=8080)
bob_app = application("Bob", host="bob.example", icon="bob16.png",
                      sessions=True, serve=8081)

Here we define a minimal application for Alice's homepage — name and photo — using legacy web standards.

alice_template = template("""\
$def with (body)
<!doctype html>
<title>Alice Anderson</title>
<body>
$:body
""")

@alice_app.wrap
def insert_header(handler, app):
    yield
    if tx.response.headers["Content-Type"] == "text/html":
        tx.response.body = alice_template(tx.response.body)

@alice_app.route(r"")
class AliceHomepage:
    def _get(self):
        return template("Alice Anderson")()

Let's do the same for Bob.

bob_template = template("""\
$def with (body)
<!doctype html>
<title>Bob Brown</title>
<body>
$:body
""")

@bob_app.wrap
def insert_header(handler, app):
    yield
    if tx.response.headers["Content-Type"] == "text/html":
        tx.response.body = bob_template(tx.response.body)

@bob_app.route(r"")
class BobHomepage:
    def _get(self):
        return template("Bob Brown")()

Nginx

Let's Encrypt for TLS.. Static files..

Browsers

alice_browser = browser("Alice")
bob_browser = browser("Bob")
alice_browser.go("alice.example")
alice.exampleGET/20038ms
alice.exampleGET/icon.png20010ms
bob_browser.go("bob.example")
bob.exampleGET/2004ms
bob.exampleGET/icon.png2001ms

Database

run_redis("web-redis.sock")
time.sleep(1)
run_queue("web-redis.sock")

Network

We'll add resources to the cache as we create them and draw the resulting graph.

the_network = cache()
the_network.add("alice.example", "bob.example")
the_network.graph
alice.exampleGET/2003ms
bob.exampleGET/2002ms

Microformats

quote_wiki("microformats")

microformats are extensions to HTML for marking up people, organizations, events, locations, blog posts, products, reviews, resumés, recipes etc.

quote_wiki("microformats2")

microformats2 is the latest stable and interoperable version of microformats.

mfs = []
for microformat, properties in mf.stable.items():
    mfs.append(f'<strong><a title="{", ".join(properties)}" '
               f'href=//microformats.org/wiki/h-{microformat}>'
               f'{microformat}</a></strong>')
HTML(f"<p>{', '.join(mfs)}</p>")

Add Identity Information

alice_homepage_header = template("""\
<header>
<h1><a class=h-card href=/>Alice Anderson</a></h1>
</header>""")

def new_homepage(self):
    return alice_homepage_header()
AliceHomepage._get = new_homepage
bob_browser.go("alice.example")
alice.exampleGET/2003ms
alice.exampleGET/icon.png20010ms
get("alice.example").mf2json
alice.exampleGET/2003ms
{
  "items": [
    {
      "type": [
        "h-card"
      ],
      "properties": {
        "name": [
          "Alice Anderson"
        ],
        "url": [
          "http://alice.example/"
        ]
      }
    }
  ],
  "rels": {
    "icon": [
      "http://alice.example/icon.png"
    ]
  },
  "rel-urls": {
    "http://alice.example/icon.png": {
      "text": "",
      "rels": [
        "icon"
      ]
    }
  }
}
get("alice.example").card
alice.exampleGET/2002ms
{
  "type": [
    "h-card"
  ],
  "properties": {
    "name": [
      "Alice Anderson"
    ],
    "url": [
      "http://alice.example/"
    ]
  }
}

Post a Note

Here we define a posts table in Alice's app database and insert her first post.

alice_note = "My first IndieWeb post!"
alice_app.db.define(posts="data JSON")
alice_app.db.insert("posts", data={"published": utcnow(), "content": alice_note,
                                   "slug": "1"})
alice_app.db.select("posts")
data JSON
{
  "published": "2020-10-17T19:39:12.321565+00:00",
  "content": "My first IndieWeb post!",
  "slug": "1"
}

Next we write a template for Alice's homepage feed and update the homepage handler to include her posts.

alice_homepage_feed = template("""\
$def with (posts)
<article class=h-feed>
<h2 class=p-name>Recent Posts</h2>
$for post in posts:
    <section class=h-entry><a href=/posts/$post["data"]["slug"] class="u-url
    p-content">$post["data"]["content"]</a></section>
</article>
""")

def _new_homepage_get(self):
    return (alice_homepage_header() +
            alice_homepage_feed(alice_app.db.select("posts")))
AliceHomepage._get = _new_homepage_get
bob_browser.go("alice.example")
alice.exampleGET/2004ms
get("alice.example").mf2json
alice.exampleGET/2003ms
{
  "items": [
    {
      "type": [
        "h-card"
      ],
      "properties": {
        "name": [
          "Alice Anderson"
        ],
        "url": [
          "http://alice.example/"
        ]
      }
    },
    {
      "type": [
        "h-feed"
      ],
      "properties": {
        "name": [
          "Recent Posts"
        ]
      },
      "children": [
        {
          "type": [
            "h-entry"
          ],
          "properties": {
            "content": [
              "My first IndieWeb post!"
            ],
            "url": [
              "http://alice.example/posts/1"
            ]
          }
        }
      ]
    }
  ],
  "rels": {
    "icon": [
      "http://alice.example/icon.png"
    ]
  },
  "rel-urls": {
    "http://alice.example/icon.png": {
      "text": "",
      "rels": [
        "icon"
      ]
    }
  }
}
alice_post_wrapper = template("""\
$def with (post_body)
<article class=h-entry>
$:post_body
</article>
""")

alice_post_content = template("""\
$def with (post)
<div class=e-content>
$:mkdn(post["content"])
</div>
<style>.e-content { font-size: 1.5em; }</style>
<p><small><a class="p-author h-card" href=/>Alice Anderson</a> <time class=dt-published
datetime=$post["published"]>$post["published"]</time></small></p>
""")

@alice_app.route(r"posts/\d+")
class AlicePost:
    def _get(self):
        slug = tx.request.uri.path.split("/")[1]
        post = alice_app.db.select("posts", where="json_extract(data, '$.slug') = ?",
                                   vals=[slug])[0]["data"]
        return alice_post_wrapper(alice_post_content(post))
bob_browser.select_first(".h-entry .u-url").click()
bob_browser
alice.exampleGET/posts/120016ms
alice_note_url = get("alice.example").mf2json["items"][1]["children"][0]\
                                             ["properties"]["url"][0]
alice.exampleGET/2003ms

POSSE

quote_wiki("POSSE")

POSSE is an abbreviation for Publish (on your) Own Site, Syndicate Elsewhere, the practice of posting content on your own site first, then publishing copies or sharing links to third parties (like social media silos) with original post links to provide viewers a path to directly interacting with your content.

# alice_browser.twitter_login("IndieWebAlice")
alice_syndication_url = "https://twitter.com/IndieWebAlice/status/1316491259628773376"
# alice_syndication_url = alice_browser.twitter_syndicate_post(alice_note, alice_note_url)
alice_browser.go(alice_syndication_url, wait=2)
alice_post_content += template("""\
<p><small>Syndicated to <a class=u-syndication
href=$post["syndication"]>Twitter</a></small></p>""")
post_data = alice_app.db.select("posts", where="json_extract(data, '$.slug') = ?",
                                vals=["1"])[0]["data"]
post_data["syndication"] = alice_syndication_url
alice_app.db.update("posts", what="data = ?", where="json_extract(data, '$.slug') = ?",
                    vals=[post_data, "1"])
alice_browser.go(alice_note_url)
alice.exampleGET/posts/12006ms

Post a Reply

quote_wiki("reply")

A reply (or comment) is a kind of post that is a text (typically, though photos are possible too) response to some other post, that makes little or no sense without reading or at least knowing the context of the source post.

bob_note = "Welcome to the party! 🎉"
bob_note_url = f"http://bob.example/posts/1"
# bob_browser.twitter_login("IndieWebBob")
bob_syndication_url = "https://twitter.com/IndieWebBob/status/1316491346354397184"
# bob_syndication_url = bob_browser.twitter_syndicate_post(bob_note, bob_note_url,
#                                                          alice_syndication_url)
bob_browser.go(bob_syndication_url, wait=2)
bob_app.db.define(posts="data JSON")
bob_app.db.insert("posts", data={"published": utcnow(),
                                 "content": bob_note,
                                 "in-reply-to": alice_note_url,
                                 "syndication": bob_syndication_url,
                                 "slug": "1"})
posts = bob_app.db.select("posts")
posts
data JSON
{
  "published": "2020-10-17T19:39:21.969077+00:00",
  "content": "Welcome to the party! \ud83c\udf89",
  "in-reply-to": "http://alice.example/posts/1",
  "syndication": "https://twitter.com/IndieWebBob/status/1316491346354397184",
  "slug": "1"
}

Reply Context

quote_wiki("reply-context")

A reply context is the display of what a reply post is in reply to, including linking to that original post with in-reply-to markup, showing some amount of that original post like author name, icon, summary / ellipsed content, and datetime published.

bob_app.cache[alice_note_url].mf2json
alice.exampleGET/posts/12006ms
{
  "items": [
    {
      "type": [
        "h-entry"
      ],
      "properties": {
        "content": [
          {
            "html": "<p>My first IndieWeb post! </p>",
            "value": "My first IndieWeb post!"
          }
        ],
        "author": [
          {
            "type": [
              "h-card"
            ],
            "properties": {
              "name": [
                "Alice Anderson"
              ],
              "url": [
                "http://alice.example/"
              ]
            },
            "value": "Alice Anderson"
          }
        ],
        "published": [
          "2020-10-17T19:39:12+00:00"
        ],
        "syndication": [
          "https://twitter.com/IndieWebAlice/status/1316491259628773376"
        ]
      }
    }
  ],
  "rels": {
    "icon": [
      "http://alice.example/icon.png"
    ]
  },
  "rel-urls": {
    "http://alice.example/icon.png": {
      "text": "",
      "rels": [
        "icon"
      ]
    }
  }
}
Authorship
quote_wiki("authorship-specification")

The Authorship specification is an algorithm for discovering and determining the author of a post.

alice_note_entry = bob_app.cache[alice_note_url].entry
alice_note_entry
{
  "published-str": "2020-10-17T19:39:12+00:00",
  "published": "2020-10-17T19:39:12+00:00",
  "author": {
    "name": "Alice Anderson",
    "url": "http://alice.example/"
  },
  "content": "<p>My first IndieWeb post! </p>",
  "content-plain": "My first IndieWeb post!",
  "syndication": [
    "https://twitter.com/IndieWebAlice/status/1316491259628773376"
  ],
  "type": "entry"
}
Nicknames Cache
quote_wiki("nicknames-cache")

A nicknames cache is a way indieweb sites store information about people to improve the user experience of the site owner referring, mention, and/or linking to those people.

bob_app.cache[alice_note_entry["author"]["url"]].card
alice.exampleGET/2004ms
{
  "type": [
    "h-card"
  ],
  "properties": {
    "name": [
      "Alice Anderson"
    ],
    "url": [
      "http://alice.example/"
    ]
  }
}
bob_post = template("""\
$def with (post, cache)
<article class=h-entry>

$if "in-reply-to" in post:
    $ reply_url = post["in-reply-to"]
    $ entry = cache[reply_url].entry
    <div class="u-in-reply-to h-cite">
        <a class=u-url href=$reply_url><blockquote
        class=e-content>$:entry["content"]</blockquote></a>
        <p><small>
            <span class="p-author h-card">$entry["author"]["name"]</span>
            <time class=dt-published
            datetime=$entry["published"]>$entry["published"]</datetime>
        </small></p>
    </div>

<div class=e-content>
$:mkdn(post["content"])
</div>
<style>
.e-content { font-size: 1.6em; }
.u-in-reply-to .e-content { font-size: 1.3em; }
.u-comment .e-content { font-size: 1.1em; }
</style>

<p><small><a class="p-author h-card" href=/>Bob Brown</a> <time class=dt-published
datetime=$post["published"].isoformat()>$post["published"].isoformat()</time></small></p>

<p><small>Syndicated to <a class=u-syndication
href=$post["syndication"]>Twitter</a></small></p>

</article>
""")

@bob_app.route(r"posts/\d+")
class BobPost:
    def _get(self):
        slug = tx.request.uri.path.split("/")[1]
        post = bob_app.db.select("posts", where="json_extract(data, '$.slug') = ?",
                                 vals=[slug])[0]["data"]
        return bob_post(post, bob_app.cache)
bob_note_url = f"bob.example/posts/{posts[0]['data']['slug']}"
bob_browser.go(bob_note_url)
bob.exampleGET/posts/12008ms
the_network.add(alice_note_url, bob_note_url, alice_syndication_url, bob_syndication_url)
the_network.graph
alice.exampleGET/posts/12008ms
bob.exampleGET/posts/12009ms

Webmention

quote_wiki("Webmention")

Webmention is a web standard for mentions and conversations across the web, a powerful building block that is used for a growing federated network of comments, likes, reposts, and other rich interactions across the decentralized social web.

alice_app.wrap(webmention.insert_references)
alice_app.mount(webmention.receiver)
webmention.send_mention(bob_note_url, alice_note_url)
time.sleep(1)
alice.exampleGET/posts/120013ms
alice.examplePOST/mentions20114ms
Alice/web.framework.indieweb.webmention:receive aBKQVGTnY http://bob.example/posts/1 http://alice.example/posts/1
bob.exampleGET/posts/120010ms
alice_browser.go("alice.example/mentions")
alice.exampleGET/mentions20019ms

Comments

quote_wiki("comments")

Comments are displayed in the context of an original post, and may be a mix of syndicated reply posts from other sites received via Webmention, as well as locally created comments.

alice_post_mentions = template("""\
$def with (mentions)
$for mention in mentions:
    $ m = mention["data"]
    <div class="u-comment h-cite">
        <div class=e-content>$:m["content"]</div>
        <p><small><span class="p-author h-card">$m["author"]["name"]</span>
        <time class=dt-published
        datetime=$m["published"]>$m["published"]</time></small></p>
    </div>
""")

def new_post(self):
    slug = tx.request.uri.path.split("/")[1]
    post = alice_app.db.select("posts", where="json_extract(data, '$.slug') = ?",
                               vals=[slug])[0]["data"]
    mentions = alice_app.db.select("mentions")
    return alice_post_wrapper(alice_post_content(post) +
                              alice_post_mentions(mentions))
AlicePost._get = new_post

alice_browser.go(alice_note_url)
alice.exampleGET/posts/120017ms

Backfeed

quote_wiki("backfeed")

Backfeed is the process of syndicating interactions on your POSSE copies back (AKA reverse syndicating) to your original posts.

# post reply from @angelogladding and backfeed
# bob_browser.twitter_backfeed_post(alice_syndication_url)
# alice_browser.twitter_backfeed_post(bob_syndication_url)

Salmention

quote_wiki("Salmention")

Salmention is a protocol extension to Webmention to propagate comments and other interactions upstream by sending a webmention from a response to the original post when the response itself receives a response (comment, like, etc.).

# carol replies to bob's reply to alice
# alice's note renders the notes of bob and carol in a nested fashion

Vouch

quote_wiki("Vouch")

The Vouch protocol is an anti-spam extension to Webmention. Webmention with Vouch depends on understanding Webmention.

# dan replies to a different alice post using carol's salmentioned reply as a vouch
quote_wiki("search")

search in the context of the IndieWeb refers to being able to search your personal site for your own content.

IndieAuth

quote_wiki("IndieAuth")

IndieAuth is a federated login protocol for Web sign-in, enabling users to use their own domain to sign in to other sites and services.

First we mount an IndieAuth server to Alice's site. This will include handlers for the /auth and /auth/token resources and insert the respective rel=authorization_endpoint and rel=token_endpoint links into the HTML and HTTP Link headers of the homepage /.

alice_app.wrap(indieauth.insert_references)
alice_app.mount(indieauth.server)

Next we create an application for a site that will publish "Hello World" to any user's personal website on their behalf. The actual act of publishing will be discussed in the next section. For now we'll mount an IndieAuth client to the HelloWorld's site. This will include a handler for /sign-in.

hello_app = application("SayHello", host="hello.example",
                        sessions=True, serve=8082)
hello_app.mount(indieauth.client)

hello_template = template("""\
$def with (body)
<!doctype html>
<title>Say Hello</title>
<body>
<h1 class=h-app><span class=name>Say Hello</span></h1>
$:body
""")

hello_homepage_template = template("""\
<p><a href=/sign-in>Sign in</a> to say hello to a friend.</p>
""")

@hello_app.wrap
def insert_header(handler, app):
    yield
    if tx.response.headers["Content-Type"] == "text/html":
        tx.response.body = hello_template(tx.response.body)

@hello_app.route(r"")
class HelloHomepage:
    def _get(self):
        if tx.user.session:
            response = template("$def with (tx)\nSigned in as $tx.user.session['url']")(tx)
        else:
            response = hello_homepage_template()
        return response

alice_browser.go("hello.example")
hello.exampleGET/2002ms
hello.exampleGET/favicon.ico40414ms
alice_browser.select_first("a").click()
alice_browser.select_first("input[name=url]").send_keys("alice.example")
alice_browser
hello.exampleGET/sign-in2002ms
alice_browser.select_first("form").submit()
alice_browser.wait_until_url_contains("alice.example/auth")
alice_browser
alice.exampleGET/20015ms
hello.exampleGET/sign-in?url=alice.example30333ms
hello.exampleGET/2001ms
alice.exampleGET/auth?me=alice.example&client_id=http%3A%2F%2F127.0.0.1%3A8082&redirect_uri=http%3A%2F%2F127.0.0.1%3A8082%2Fsign-in%2Fauth&response_type=code&state=na0vipcWPh&scope=draft20025ms
alice_browser.select_first("form").submit()
alice_browser
alice.examplePOST/auth?me=alice.example&client_id=http%3A%2F%2F127.0.0.1%3A8082&redirect_uri=http%3A%2F%2F127.0.0.1%3A8082%2Fsign-in%2Fauth&response_type=code&state=na0vipcWPh&scope=draft30228ms
hello.exampleGET/sign-in/auth?state=na0vipcWPh&code=t4z_kACRPU3034ms
hello.exampleGET/2006ms

Micropub

quote_wiki("Micropub")

Micropub is an open API standard (W3C Recommendation) for creating, editing, and deleting posts on websites, like on your own domain, supported by numerous third-party clients and CMSs.

alice_app.mount(micropub.server)
hello_publish_template = template("""\
$def with (contacts)
<form method=post>
<ul>
$for contact in contacts:
    <li><input type=checkbox name=friends value=$contact> $contact</li>
</ul>
<button>Say Hello!</button>
</form>
""")

hellos = ("hey", "hiya doin'", "what's happenin'",
          "what's up", "how's it goin'")

def _new_homepage_get(self):
    if tx.user.session:
        contacts = ["bob", "carol", "dan"]  # TODO q=contacts
        response = hello_publish_template(contacts)
    else:
        response = hello_homepage_template()
    return response
HelloHomepage._get = _new_homepage_get

def _new_homepage_post(self):
    if not tx.user.session:
        raise SeeOther("/sign-in")
    message = random.choice(hellos)
    friends = form("friends", friends=[]).friends
    # micropub.send_request(tx.user.url, in_reply_to=friends,
    #                       content=message)
    return template("""\
$def with (message, friends)
<p>Sending <q>$message</q> to $", ".join(friends) ..</p>
""")(message, friends)
HelloHomepage._post = _new_homepage_post
alice_browser.go("hello.example")
hello.exampleGET/2006ms
bob_app.wrap(webmention.insert_references)
bob_app.mount(webmention.receiver)
alice_checkbox = alice_browser.select_first("input[type=checkbox]")
alice_checkbox.click()
alice_checkbox.submit()
alice_browser
hello.examplePOST/20011ms

Person Mentions

quote_wiki("person-mention")

person mention is a homepage webmention sent to a person's homepage.

bob_browser.go("bob.example/mentions")
bob.exampleGET/mentions20013ms

Web Actions

quote_wiki("web-action")

A web action is the interface and user experience of taking a specific discrete action, across the web, from one site to another site or application.

AutoAuth

quote_wiki("AutoAuth")

AutoAuth is the working title of an extension to IndieAuth that allows clients to authorize to other servers in the name of their user, without the user being present to confirm each individual authorization flow.

Private Webmention

quote_wiki("private-webmention")

The Private Webmention protocol is an extension to Webmention that supports sending and verifying Webmentions for posts that require access control.

# alice sends a private message to bob
# carol sends a private message to the group alice, bob and dan

WebSub

quote_wiki("WebSub")

WebSub (previously known as PubSubHubbub or PuSH, and briefly PubSub) is a notification-based protocol for web publishing and subscribing to streams and legacy feed files in real time.

alice_app.mount(websub.hub)
bob_app.mount(websub.subscriptions)
alice_browser.go("alice.example/hub")
alice.exampleGET/hub20011ms
alice.exampleGET/favicon.ico20010ms
bob_browser.go("bob.example/subscriptions")
bob.exampleGET/subscriptions2007ms
bob.exampleGET/favicon.ico20023ms

Microsub

quote_wiki("Microsub")

Microsub is a proposed standard for creating a new generation of social readers that decouples the management of subscriptions to feeds and the parsing/delivering content from the user interface and presentation of the content.

# alice_app.mount(web.microsub.server)  # /microsub
# alice_app.mount(web.microsub.client)  # /reader
# alice_browser.go("alice.example/reader")

JF2

quote_wiki("jf2")

jf2 is a working prototype of a simpler JSON serialization of microformats2 intended to be easier to consume than the standard Microformats JSON representation.

# alice_homepage.jf2

To Do

Hosting

quote_wiki("short-domains")

short domains are commonly used on the IndieWeb for personal URL shortening, or clever domain hacks.

quote_wiki("free-domain-names")

Free domain names are provided by certain domain name registrars.

quote_wiki("web hosting")

Web hosting can be the primary regular cost in maintaining an IndieWeb site; this page lists several options from free on up depending on your publishing needs, like a static, shared, private, or dedicated server.

Profile

quote_wiki("icon")

An icon in the context of the indieweb typically refers to a home page icon for an indieweb site, or small decorative images to indicate types of posts.

quote_wiki("featured photos")

featured photos are a profile feature that allows the user to pick a few photos to show in a box on their profile, supported by Facebook (since 2016-03-29).

quote_wiki("rel-me")

Using rel=me on a hyperlink indicates that its destination represents the same person or entity as the current page, which is a key building-block of web-sign-in and IndieAuth.

quote_wiki("blogroll")

A blogroll is a list of other sites that you read, are a follower of, or recommend.

Posts

quote_wiki("URL design")

URL design is the practice of deliberately designing URLs, in particular, permalinks, typically for a better UX for everyone who creates, reads, and shares content.

quote_wiki("permalink")

A permalink is a URL which typically represents and retrieves a single post (also explicitly called a post permalink).

quote_wiki("permashortlink")

A permashortlink (abbreviated PSL) is a URL using a short-domain that expands to a permalink; on the IndieWeb, PSLs use personal short domains to expand to the same person's personal domain, thus minimizing the fragility often associated with shortlinks.

quote_wiki("media fragment")

media fragment is a way of linking to a part of a longer media file.

quote_wiki("link-preview")

A link preview is what posts show about one or more embedded links, e.g.

quote_wiki("hovercard")

A hovercard is a brief overview, usually including thumbnail, name, and description, that is shown in a rectangle overlapping in-context when you hover over something like a link, typically richer than a plain text tooltip.

quote_wiki("facepile")

A Facepile is a way of showing interactions for a page/site by using profile pictures.

quote_wiki("post-type-discovery")

Post Type Discovery specifies an algorithm for consuming code to determine the type of a post by its content properties and their values rather than an explicit “post type” property, thus better matched to modern post creation UIs that allow combining text, media, etc in a variety of ways without burdening users with any notion of what kind of post they are creating.

quote_wiki("posts")

Post or posts may refer to individual pieces of content published on an indieweb site such as notes, articles, & responses, or the act of creating the aforementioned content (present tense), or Posts about the IndieWeb.

quote_wiki("private posts")

private posts refer to posts or portions of posts which are private to either the author or to a limited audience chosen or previously approved by the author.

quote_wiki("note")

A note is a post that is typically short unstructured* plain text, written & posted quickly, that has its own permalink page.

quote_wiki("article")

An article is a kind of post that typically has more structure than a simple note. Articles usually have a name (title), multiple paragraphs, and often subheadings, blockquotes, embedded images, and a footer of references or citations.

quote_wiki("event")

An event is a type of post that in addition to a post name (event title) has a start datetime (likely end datetime), and a location.

quote_wiki("payment")

payment in the context of the indieweb refers to a feature on an indie web site that provides a way for the visitor to that website to pay (currency, gift card credit, etc.) the person represented by that indie web site.

quote_wiki("read")

To read or reading is the act of viewing and interpreting posts or other documents; on the IndieWeb, a read post expresses that something has been read, like a book or section thereof.

quote_wiki("photo")

A photo is a post whose primary content is a photograph or other image, with an optional caption. With multiple photographs it becomes a multi-photo post.

quote_wiki("bookmark")

A bookmark (or linkblog) is a post that is primarily comprised of a URL, often title text from that URL, sometimes optional text describing, tagging, or quoting from its contents.

quote_wiki("responses")

responses, or interactions, in the context of the indieweb, refer to all the different ways and things people explicitly do to and with others’s posts, from written replies to quick likes, in other words responses = replies + reactions.

quote_wiki("repost")

A repost on the indieweb is a post that is purely a 100% re-publication of another post. The act of reposting is an umbrella term that covers the general practice of republishing another post typically on the same service or silo, but more and more across sites.

quote_wiki("rsvp")

An RSVP is a reply to an event post that says whether the sender is or is not attending, might attend, or is just interested in the event.

quote_wiki("like")

like is a popular webaction button and in some cases post type on various silos such as Facebook and Instagram (uses a heart ♥). It is similar to but different from a favorite.

quote_wiki("reacji")

reacji is an emoji reaction, the use of a single emoji character in response to a post, introduced as a feature by Slack[1].

quote_wiki("annotations")

Annotations are comments (including marginalia), highlights or any other interactions that add to (part of) a post, typically added by individuals other than the author.

quote_wiki("marginalia")

Marginalia are responses to parts of a post, rather than posts as a whole; they are typically published (on another site) with a fragmention link to the specific part (like a paragraph), notified via Webmention, and displayed on the post adjacent to the referenced part (like sidebar comments next to paragraphs).

quote_wiki("feed")

A feed is a dynamic set of posts, typically listed in reverse-chronological order, often only the most recent n (like 10) of them.

Silos

quote_wiki("silo")

A silo, or web content hosting silo, in the context of the IndieWeb, is a centralized web site (like most social media) typically owned by a for-profit corporation that stakes some claim to content contributed to it and restricts access in some way (has walls).

quote_wiki("algorithmic feed")

algorithmic feed (AKA algorithm-driven feed or just algorithm feed) is a more correct term for the "algorithmic timeline" lie, and an increasingly common feature on social media silos such as Instagram, Facebook, and Twitter, where they show only some posts from your followings, as well as show some posts only hours or days after they were posted, thus not in chronological order.

quote_wiki("algorithmic timeline")

algorithmic timeline (sometimes non-chronological timeline) is a doublespeak phrase propagated by silos (and some popular media) to refer to social media algorithmic feed feature(s), as a timeline is "a display of a list of events in chronological order"[1], whereas silos now (since 2016+) use "timeline" to refer to often out of chronological order display of aggregations of following's posts which still presentationally resemble previous chronologically ordered displays.

quote_wiki("PESOS")

PESOS is an acronym/abbreviation for Publish Elsewhere, Syndicate (to your) Own Site.

quote_wiki("PASTA")

PASTA is an acronym/abbreviation for Publish Anywhere, Save To (private) Archive, the practice of automatically saving a copy of whatever you post on (social media) silos to someplace else under your own control, like a private directory on your own server, or a local folder on your laptop that is less vulnerable to site-death.

Tagging

quote_wiki("tag-reply")

A tag reply is a special edit response to a post that tags that post with one or more tags, including possibly (or only) person-tags.

quote_wiki("untag")

untag is sending an edit request to delete one or more tags (often person-tags) on someones post, where those (person-)tags possibly originated from someone else's tag-of reply post.

quote_wiki("person-tag")

A person tag (AKA people tag) is a tag on a post that refers to a specific person by URL, and which the post author explicitly adds as a tagging action, beyond just mentioning a person via hyperlink, h-card, or @-name (autocompleted or not).

Social

quote_wiki("feed reader")

A feed reader (AKA RSS reader) is an application (local or on the web) that subscribes to feeds (often limited to legacy Atom & RSS) and presents them in an interface for reading, typically without any way to respond, in contrast to a social reader.

quote_wiki("reader")

A reader in the context of the indieweb is the portion/feature integrated into an indieweb site that provides a way to read content from other indieweb sites and respond (like, comment, repost, etc) inline on the site itself directly while reading posts.

quote_wiki("social reader")

A social reader is a modern interactive reader that allows you to directly respond to posts (with a like, comment, etc) right there inline with posts as you read them (as people do in social media), in contrast to legacy feed readers which were one-way read-only experiences and provided no mechanisms to interact with or respond to posts.

quote_wiki("follow")

follow is a common feature (and often UI button) in silo UIs (like Twitter) that adds updates from that profile (typically a person) to the stream shown in an integrated reader, and sometimes creates a follow post either in the follower's stream ("… followed …" or "… is following …") thus visible to their followers, and/or in the notifications of the user being followed ("… followed you").

quote_wiki("block")

Block is a feature on many silos that provides the ability for one user to "block" or prevent interactions from another user.

quote_wiki("allowlist")

An allowlist (or allow list) is a list of individuals or domains who are expressly permitted by the site using it.

quote_wiki("block list")

A block list is a list of accounts that a user has blocked on a service or site.

Miscellaneous

quote_wiki("image proxy")

An image proxy is a service that fetches and re-serves images from a new URL.

quote_wiki("js;dr")

js;dr is JavaScript required; Didn’t Read.

quote_wiki("authorization-endpoint")

An authorization endpoint is an HTTP endpoint that micropub and IndieAuth clients can use to identify a user or obtain an authorization code (which is then later exchanged for an access token) to be able to post to their website.

quote_wiki("token-endpoint")

A token endpoint is an HTTP endpoint that micropub clients can use to obtain an access token given an authorization code.

quote_wiki("media-endpoint")

micropub media endpoint is a Micropub endpoint that exclusively handles file uploads and returns a URL that can be used in a subsequent Micropub request.

quote_wiki("scope")

In OAuth terminology, scope is a way to limit what parts of your account are accessible by third-party applications.

alice_app.server.kill()
bob_app.server.kill()
hello_app.server.kill()

alice_browser.quit()
bob_browser.quit()
alice_app.db.destroy()
bob_app.db.destroy()
hello_app.db.destroy()
# kill_redis("web-redis.sock")

Last updated

CC BY 4.0

canopy powered