yapern

Create a message

B I S x2 H “” </> •••

0/2000

Create new sub-yap

← Blog

A live message board in a static site

· Yapern

Click an up arrow in Yapern’s homepage preview and the score changes. Refresh and your vote is gone. Scroll down to Latest messages, open a post, and you’re using a different part of the site: public records fetched from Mainbrella, with accounts and replies saved through its API.

Yapern is an open source discussion board organized into sub-yaps, communities with their own posts and URLs. The frontend ships as HTML, CSS, and JavaScript. The conversations live in Mainbrella. The first live-message implementation briefly added an API server to Yapern, which the next commit took back out.

The actual homepage preview shows sample posts in sub-yaps such as technology and making, with voting and save controls. These interactions use local page state.

The API server lasted nine minutes

The first live-message commit on October 18 added a Cloudflare Worker at /api/messages. The browser called Yapern; Yapern called Mainbrella’s table API; the Worker checked the response and returned the messages. Development had its own middleware for the same route.

About nine minutes later, the next implementation removed the Worker and the development proxy. The browser made the table request directly and checked the response itself. The hosting configuration went back to static assets.

For this app, that’s a good deletion. Mainbrella already hosts the data API, so routing a read through Yapern adds another service to deploy and another place for a request to fail. The browser still needs to handle bad data and unavailable service; removing a hop doesn’t remove those jobs.

Before: the browser requests messages through a Yapern Worker, which calls Mainbrella BFF. After: the browser calls Mainbrella BFF directly, removing the intermediate Worker.

A public browser key accompanies those requests. Signed-in requests also carry a user session. The public key is shipped with the frontend, so it can’t be treated as a secret. Mainbrella’s backend grants and row policies have to decide which operations are allowed. Hiding a button in JavaScript would be a pretty flimsy substitute.

A post needs an address

A feed of the newest twenty messages can look complete until somebody wants to send an older post to a friend. Yapern gives each sub-yap a route and each post a route inside it. For a hypothetical making sub-yap, the URLs have this shape:

/y/making
/y/making/<message-id>

Both load the same static board.html. A hosting rewrite keeps the requested URL, and the browser reads it to decide what to fetch. For a post, it first looks up the sub-yap by its slug, then requests the message using both that board’s ID and the message ID. The post doesn’t need to appear among the newest twenty to be reachable.

This also gives a wrong address a sensible outcome. A missing sub-yap or a message that doesn’t belong to it produces an inline not-found state. The client checks returned board and message IDs as well as sending filters. Those checks catch an unexpected response; authorization still belongs to the backend.

Saving takes more than closing the dialog

Accounts and the composer added writes to that read path. After signing in with email and password, you can create a sub-yap or post to an existing one. The frontend sends the current user’s session to Mainbrella and waits for a saved row before navigating to the new post.

The client checks that the returned row has an ID, belongs to the signed-in user, and contains the values it submitted. If the response can’t confirm the save, the dialog keeps the input. A connection can drop after a backend has saved a post, so “the request failed” isn’t enough to conclude “nothing was posted.” Yapern’s error asks you to check the sub-yap before trying again.

Replies followed later that evening. They have their own table, and each reply points to an original message. That keeps discussion to one level. The reader fetches replies oldest first in batches of one hundred, rather than silently stopping at the first batch.

What survives a refresh today
InteractionWhere it livesAfter a refresh
Preview votes and savesPage memoryReset to the sample
Live posts and repliesMainbrella tablesFetched again from the API
Live votingUnavailableNo vote is saved

That last row is easy to miss if you only look at screenshots. Live posts have vote arrows and a fixed count of one, but the controls are marked as unavailable. The preview’s moving scores don’t establish a shared voting system. There’s still work to do there.

A link preview can fail without losing the post

The October 19 change adds an optional image lookup when you put a URL in the composer. After a short delay, the browser asks Mainbrella for the linked page’s preview image. Changing or removing the URL cancels the previous lookup so an old result doesn’t replace the new link’s preview.

The image is only a preview in the open dialog. The saved message body contains the URL and any text you wrote; the change adds no image column to the message table. If the lookup fails or the image won’t load, you can still post. A flaky image server shouldn’t get a veto over a conversation.

Yapern’s frontend can now be rebuilt and redeployed without copying its conversations into the build. Mainbrella keeps the saved rows, and the post URL tells the reader which ones to fetch. Create a post, copy its URL, and come back to it after a refresh. The post and its replies are read back from the API, and your next reply goes back to that thread.