Skip to content

Allthing 0.5.0

Latest

Choose a tag to compare

@ael-dev3 ael-dev3 released this 23 Oct 00:54
7b16efd

Added

  • A signed-in reader's front page ranks the casts of the people they follow. Hot and top on / read /api/v1/following (the session's account's follows, from Hypersnap's feed/following, cached per account and never for another reader) and rank them as before, with Show less and favorites; a Following / Everyone menu beside the sort tabs switches to the trending feed and its time ranges (?feed=everyone). Where they follow few who cast lately, up to 40 trending casts follow theirs, and a note says so. Signed out, nothing changes; early paint skips a signed-in reader's front page.

  • Show mini app casts: a switch in the account menu, over any feed that has mini app casts ("3 mini app casts hidden"), and on a mini app post. On, feeds, channels, profiles and threads show casts with mini apps or frames, each with a "Mini app · Open in Farcaster" link; Allthing still doesn't run them. Off is the default. The choice is kept in this browser and followed by its other tabs. The Worker now marks mini app casts instead of dropping them (one payload for everyone, no new requests), and the index keeps them; spam is left out as before, and search, reply previews and notifications still leave out both.

  • @mention suggestions in the composer, for casts, replies and quotes: typing "@name" lists matching people under the caret, those you follow first and marked "Following"; arrows move, Enter or Tab picks, Escape closes.

  • A reply's page shows the casts it answers as full cards, root first, each avatar joined to the next by a line, in place of the dashed "In reply to" snippet. A chain of more than three folds its middle behind "Show N earlier", which opens in place. The cards keep their media, counts, actions and hover cards, and hidden casts are left out as elsewhere.

  • The encryption for end-to-end encrypted messages, as pure, tested Rust (allthing_core::e2e). After the browsers' X25519 agreement, it uses HKDF-SHA256, with both parties' identities, key versions and public keys in its info, and then XChaCha20-Poly1305, with a random 24-byte nonce, over the text padded to 32 bytes. The associated data binds the sender, the recipient and their key versions and keys. The tests pin a key and a ciphertext built on RFC 7748's keys, which tests/e2e-vectors.test.mjs recomputes with Node's own crypto.

  • Verified Farcaster accounts on SpacetimeDB. Signed in, a browser's SpacetimeDB identity gets the Worker's word that it is your account (POST /api/v1/auth/attest, an Ed25519 signature by a key of its own, the new ATTEST_KEY secret, valid 10 minutes), and the module's new verify_fid reducer checks it against the key's public half pinned in the module and marks the account verified. Once the key is pinned, only verified accounts can chat or send messages there. node scripts/attest-key.mjs | npx wrangler secret put ATTEST_KEY makes the key; /api/v1/status says spacetime.attest and the public half (never the key). A test runs it on the real Worker.

  • The SpacetimeDB module behind Messages (the one inbox and its end-to-end encrypted messages) is in the repository (spacetime/), as published to maincloud as allthing, with its checks in CI: formatting, Clippy, unit tests of what it accepts, the wasm32 build and the license audit (sh spacetime/check.sh).

  • Protocol test vectors for the messages Allthing signs (crates/allthing-core/tests/message_vectors.rs). The encoder is checked against Snapchain's own test vectors (two casts from its engine tests, signed by its test key), the example like in Snapchain's HTTP API reference, and five messages on mainnet as Hypersnap served them: a like, a recast, an unlike, a quote and a deleted cast. Each is rebuilt from what the node says it holds and must hash to the very hash its signer signed, and each signature is verified against its signer's key (ed25519-dalek, a test-only dependency: Allthing signs with the browser's WebCrypto).

  • The composer's bar is as compact as the Farcaster app's, and it posts with Cmd or Ctrl and Enter. Its tools are 32 px icons on the left (attach, emoji, sticker, GIF, the clock; a 40 px tap on a phone), and on the right a ring shows how long the text is, as in the Farcaster app: it fills toward a cast's 320 bytes, then toward a long cast's 1,024, turns amber in the last 75 bytes and gives way to the bytes left under 25, red and below zero past the limit (a screen reader hears it only as it crosses those lines). Cmd or Ctrl and Enter in the text box posts, but not in the GIF search, where Enter searches; a reply's button reads "Replying…" while it goes. Signed in, a post page fetches the composer's code and the GIF key once it is idle, so the first Reply opens with its bar, with no "Loading…"; a press on a touch screen starts the fetch too.

  • A reply shows what it answers, as the Farcaster app's composer does. Above the text box, which now says "Cast your reply": the author's picture, name, @username and how long ago, the first five lines of the cast (three on a phone) with its Pepes, its first four images and videos as squares of a fixed size (96 px, 64 on a phone; a video with its poster and a play sign), and "Replying to @username", with a line from the author's picture down to yours beside the box. It is drawn from the cast on the page, so opening it asks for nothing, the box has focus by the next frame, and nothing moves as its pictures load. Screen readers hear one line with the box: "Replying to @alice: " and the start of the cast. A scheduled reply you change shows its line as before.

  • Notifications in the app, for a signed-in reader. A bell beside your picture in the header counts what's new ("9+" past nine), from the first page of your notifications, read when the app starts, every 3 minutes while the page is in view, and when you come back to it. /notifications lists them grouped as the Farcaster app groups them, under the tabs All, Mentions (mentions, and quotes that mention you) and Replies: each row has its kind's icon, up to five faces, what happened ("alice and 5 others liked your cast"), the cast's start with its Pepe, and when. A row opens its cast, or for follows the newest follower's profile, and a face or a name opens that person's profile card. What came since your last visit is tinted for this visit, and opening the page marks it all seen, so the bell clears, in your other tabs too. Load more goes further back. On a phone each face, name and tab takes a 44 px tap. Your browser keeps what you've seen, by account; the static copy has neither the bell nor the page. The page's code comes as the bell is pointed at or touched (24.9 KB at brotli 11), and the module grows by 5.6 KB at brotli 11 (494,256 → 499,893 bytes).

  • Notifications, from the Worker: /api/v1/notifications gives a signed-in reader who liked, recasted, replied to, mentioned and quoted their casts, and who followed them, a page at a time, grouped as the Farcaster app groups them ("alice and 5 others liked your cast", "alice and 3 others followed you"). Hypersnap's notifications list a recast as one more like, and a mention or a quote as a reply: the hub's newest recasts of the casts liked tell recasts from likes, each cast tells replies from mentions and quotes, and the quotes of your newest casts, which Hypersnap doesn't list, are read on their own. Replies, mentions and quotes that are spam or mini apps are left out, as feeds leave them out. It answers only a session, for the session's own account, is cached at the edge for a minute per account, and no browser keeps it. What you've seen is kept by your browser, not the server.

  • Images and videos in the composer. In a new cast, a reply or a quote, the attach button (on a phone, your photos), a pasted screenshot or files dropped on the composer attach PNG, JPEG, GIF and WebP images and MP4, MOV and WebM videos. Each uploads at once, with its progress and Remove, which stops it; one that doesn't go says why, with Retry. The cast embeds them in order, two embeds at most with its GIF, sticker or the cast it quotes, and the buttons say when there's no room. A photo's location, camera and other metadata are taken out in your browser before it's uploaded (JPEG, PNG and WebP; GIFs and videos go as they are). Handed to the Farcaster app, the draft carries them; scheduled, it keeps them. Where a server has no R2 bucket bound, the composer has no attach button and takes no files.

  • Every button, tab, chip and link answers a press at once, as an app's do: in the frame after a finger, the mouse or Space or Enter presses it, it shrinks a little and dims, and it eases back over 90 ms when let go. Nothing around it moves. A link in a line of text only dims, and so does everything with reduced motion. On a phone the browser showed no press at all (Chrome shows :active only once a touch has lasted a moment, Safari only on pages that listen to touches), and the Cast button's and the heart's eased in, so their first frame showed nothing; the smoothness check now finds all 22 controls it presses, on the front page, in the composer, on a post and on a profile, answering in their first frame at 390 and 1280 px. A tap on a control is no longer taken for half of a double-tap zoom.

  • Image and video attachments, the Worker's part. POST /api/v1/media takes a signed-in reader's PNG, JPEG, GIF or WebP image (up to 10 MB) or MP4, MOV or WebM video (up to 50 MB), sent from Allthing itself, and keeps it in R2; https://allthing.social/m/<fid>/<id>.<ext>, the URL a cast embeds, serves it to anyone, in any Farcaster app: in ranges (as Safari's video player asks), with a year's caching, only ever as the type it was uploaded as, with nosniff and a sandbox policy, so a file that isn't what it says can't run as a page. The Worker hands the upload's body to R2 as it comes and never reads it, so an upload takes the same CPU time at any size: the Worker's WebAssembly memory stayed at 1.31 MiB through two 45 MB uploads. A reader may upload 300 MB and 60 files a day, 20 a minute; everyone together, 2,000 a day; and the files kept stay under 9 GiB, within R2's free 10 GB-month, counted in D1's new media table. GET /api/v1/media tells the composer whether it may attach; its button comes next. Until R2 is enabled and the bucket bound (docs/DEPLOY.md#attachments-r2, nothing else needed: no plan change, public bucket or token), uploads and /m/ answer 404 and /api/v1/status says media.ready: false.

  • Smoothness budgets in CI (e2e/smoothness.fixture.spec.ts, docs/PERFORMANCE.md#smoothness). On fixture data, a signed-in reader at 390 px (touch, the CPU slowed 2×) and at 1280 px taps the sort tabs, opens a likes count, likes, opens the composer, opens a cast and a profile from three places in the feed and goes Back, goes Back deep in the feed, scrolls, and presses nine controls, each three times. The median must answer within 100 ms (INP), with no long task overlapping it, no blank or white frame in a navigation, nothing moving after a navigation's first frame (layout shift < 0.01), the reader's card back within 2 px, and every press shown in its first frame. What the app doesn't meet yet runs as an expected failure with what it measured: no press shows in its first frame, every navigation has 2 to 6 frames with nothing to read, and opening a cast or a profile on the phone takes 136 to 232 ms, most of it drawing the page in the tap's own task.

  • The logo as a PNG, for the picture of Allthing's Farcaster account, @allthingapp: https://allthing.social/pfp.png, since Farcaster apps show an SVG picture such as favicon.svg as blank. It is the favicon's serif "A" on its ground, 512×512 and opaque, with the "A" well inside the circle apps crop a picture to. It is cached for a year, so a new logo gets a new file name.

  • Pepes show in the composer as you type them. A shortcode becomes its Pepe as soon as its closing colon is typed (":pepe_smug:" shows the smug Pepe), in a new cast, a reply and a quote, while the draft and the cast that posts keep the shortcode. The caret, selections, Backspace, the arrow keys, Enter, paste, copy and cut work as in a text box: Backspace takes a Pepe away whole; a paste is plain text, its shortcodes shown as Pepes; a copy gives the shortcodes. Undo and redo step through the draft. The text box is now editable text rather than a <textarea>, with the same look, size and label.

  • On a phone, the composer sits on the keyboard, as the Farcaster app's does. Its head has Cancel on the left and Cast, Reply or Quote on the right, where the keyboard can't cover them, and its tools are right above the keyboard, following it as it comes and goes. The tap that opens it brings the keyboard up at once on iOS too, with the text box focused.

  • Cast on your own profile. Signed in, your profile has the feeds' Cast pill at the top right of its header, where Favorite and Show less are on anyone else's, and on a phone in the row under your picture and name, as no round button floats over a profile. It opens the composer for a new cast, in no channel; Escape closes it and puts the focus back on Cast. On a touch screen the pill takes a tap 44 px tall, as the other small targets do. It isn't on anyone else's profile or for a reader signed out, and the static copy, which has no sign-in, has none.

  • The Worker can sign a reader in by their approval of Allthing's key, so one approval in the Farcaster app can both sign in and let them write from here. POST /api/v1/auth/key takes the account, the key and the key's Ed25519 signature of the sign-in's nonce for this site; it signs the reader in when the nonce is this browser's, the signature is good (checked by the runtime's WebCrypto) and Hypersnap lists the key among the account's, added by approving a request from Allthing's account. start says when this works (keys, where the Worker endorses keys), and a browser part way through a sign-in may have its key endorsed. A test runs it on the real Worker, with a custody key made up for the run and a stand-in node, and checks every refusal. The sign-in dialog uses it in a follow-up.

  • The emoji picker names each Pepe's maker, as its credit does, beside its shortcode when it is pointed at or focused (":pepe_no: by lalalexuss"), for stickers too. node scripts/check-emoji-sources.mjs reads every Pepe's source and checks that it is still there under the terms its credit gives, by the maker it names. On 21 October 2026 all 219 emoji and 32 stickers were; :pepe_eat:'s maker has deleted their 7TV account, which its credit already said, so the picker names no one for it. A unit test keeps the picker's makers the same as the credits.

  • A test of scheduled casts from end to end, on the real Worker. Two casts are scheduled from the composer for the same minute and kept in a fresh local D1. The cron, run before that minute, gives the node nothing, and at it gives the node each cast byte for byte as the browser signed it. The node is a stand-in, which takes one and refuses the other's key; the list then shows one posted, with its link, and the other not, with why, and the next run gives the node nothing more. The Worker sends what it would send Hypersnap's node to that stand-in when its HYPERSNAP_ORIGIN variable names a local port; it ignores anything else, and the variable is never set where Allthing is deployed.

  • Pepe reactions on feed cards. A cast's emoji reactions show as chips under its card on the front page, new, channels and profiles, as they do under a post: one per Pepe, with its count, most first. A listing doesn't carry names, so a chip says how many accounts reacted, with "you" for your own. A chip reacts with its Pepe too, and your own takes your reaction back, there on the card, without opening the post; your reactions this visit show on their casts' cards as well. The chips' code is fetched the first time a card has some (4.4 KB at brotli 11, and 10.5 KB it shares with a post's page, which the app fetches a few seconds in anyway), and until it comes an empty row of their height holds their place, so the cards below don't move; if it can't come, the cards go without them. The module grows by 1.8 KB at brotli 11 (459,799 → 461,590 bytes), where the chips in it would have added 9.5 KB. Reactions from before the follower first ran aren't there, and the static copy has none.

  • Allthing's poll on the front page: "Would you like an Allthing mobile client?", Yes or No. On a wide screen it sits beside the feed and stays in view as you scroll; on a phone it is a card after the fourth cast. It moves nothing on the page. Anyone can vote, signed in or not. A vote shows how many voted each way at once ("Yes 63% (5)", how many votes, and yours), and this browser keeps it, so a later visit shows the results without asking; before a vote there are no numbers. One vote counts per network (an IPv4 address, or an IPv6 address's /64), so people who share an address share a vote, and a network that has voted is told so, never how. POST /api/v1/poll keeps, in D1 (migrations/0008_poll.sql), an HMAC of the poll and the network keyed with the Worker's SESSION_SECRET, and the choice: never the address. The front page's payload carries the counts (poll), and /api/v1/status says whether votes are counted (poll.ready); without the secret the poll hides itself. The static copy has none.

  • Pepe reactions are kept for feed cards. The active-accounts cron already reads every message merged into Farcaster, every 2 minutes; it now also keeps the replies that are one Pepe and nothing else (emoji reactions) by the cast they answer, in D1's new reactions table. It drops those deleted, revoked or pruned, and those older than 400 days. The front page, new, channel and profile payloads carry their casts' reactions (notes.reactions), read with one D1 query, for chips on feed cards in a follow-up. Reactions sent before the cron first runs with this aren't there. No other cast text is kept, or even parsed.

  • Your own reaction chip takes your Pepe reaction back. Pressed again, it deletes the reply that is the reaction, at once with your key; where Allthing doesn't post for you, the Farcaster app opens that reply for you to delete there. The chip's count goes down by one and it's unpressed, and you can react with it again from there. On a phone, a chip just tapped no longer keeps the colour of one that's yours.

  • docs/DEPLOY.md lists the live checks to run once the ALLTHING_CUSTODY_KEY secret is set: the status names the custody address, a reader approves Allthing once, a like goes out and stays, a cast shows "via Allthing", and an approved reader gets no hand-off to the Farcaster app.

  • The source on GitHub, a click away: the footer's Source link has GitHub's mark, and the avatar menu links to Allthing on GitHub, so it's in reach on a feed that scrolls without end. Both open GitHub in a new tab, so the page you were reading stays where it was.

  • A profile's website. The website an account sets on its Farcaster profile shows under its bio as a link, with a link icon, on one line: "danromero.org", without the scheme, a leading www. or a trailing slash, and a long one cut short with an ellipsis. It opens in a new tab (rel="noopener noreferrer nofollow ugc"), and its link is 24 px tall with a mouse and 44 px on a touch screen. Only a web address links: http or https (a bare example.com gets https), with a host that has a dot or is an IP address; javascript:, data:, mailto: and every other scheme, a name and password before the host, spaces, control and invisible characters, and values over 256 bytes are refused, and a host in another script shows as its ASCII form (xn--…), which can't pass for a look-alike. The page checks the link again and makes its label from it, so the label always names where it goes. Hypersnap's user objects don't carry the website, so a profile's build reads the account's user data (/v1/userDataByFid, about 5 KB) alongside its casts: one more subrequest when a profile is built anew, none when it is served from the cache. A read that fails never fails the profile: it keeps the previous copy's website, and the profile is refreshed in 30 seconds. The static copy shows it too; hover cards don't, as casts don't carry it.

  • Stickers move in the picker, as on Telegram: each one in view plays, in a taller panel (four rows on a phone, five larger stickers to a row on a computer), and one held down shows large until let go, without picking it. Each moves as a small animated WebP made from its GIF, loaded as it comes into view; with reduced motion, or when your phone or browser saves data, they stay still.

  • A profile card opened from a Liked by, Recasted by or Replies preview is complete sooner: its people's votes and wallets load as the preview opens (it opens once the pointer rests on the count, or on a tap), not once the pointer comes into the list, and the name pointed at or touched goes first. Three previews' people are preloaded every 5 minutes, up from two.

  • On a phone, small targets take a fingertip: the heart and recast icons, the counts, Reply, Quote and permalink, the sort and profile tabs, Home, Sign in, Follow, Favorite, Show less and the footer's links each take a tap 44 px tall (Sign in 44 px wide too), centred on them, with the layout as it was. The replies count is as tall as the others on a computer too.

  • What you hand to the Farcaster app shows here at once, and settles once it's done there. Signed in, a like, a recast, a follow or a Pepe reaction shows straight away, pulsing gently; a cast, a reply or a quote shows where it will be (a reply above its post's replies, with the reply count up by one, and a cast or a quote above the feed) with "Posting in Farcaster". The browser asks Farcaster's network every 3 seconds (every 10 after a minute, and at once when you come back to the page) whether you've done it there; then it stops pulsing, a cast shows "Posted" with a link to it, whatever you changed in the app, the dialog closes, and a notice says so ("Liked.", "Posted. View"). A delete shows once it's done. Close the dialog without going to the app and it's taken back, though it still shows if you did it on your phone; not done in time (3 minutes, or 10 for a cast), it goes, and your draft is back in the composer. If it was done already (a like older than your latest), the button just turns, and the count stays as it is. Your latest likes and recasts, from any app, show as yours too, and the heart or the recast icon of one opens "Unlike in Farcaster" or "Undo recast in Farcaster". The dialogs say less: the title says what to do, with a line only where it helps. These are public reads of the network, from your browser.

  • 40 more Pepe emoji, 22 of them animated: 219 in all, 159 animated. For the season, Pepe in a pumpkin hat, a skeleton costume, a witch's hat, as a vampire, as Santa and in the snow; dressed up as a cowboy, a pirate, a knight, a viking, a firefighter, a farmer and an artist; out riding a bike, doing yoga or pull-ups, fishing, floating on a cloud and in the sun under a rainbow; eating a banana or watermelon, with bread, toast, an egg and cheese; and new reactions: :pepe_plot:, :pepe_saved:, :pepe_yep:, :pepe_sure:, :pepe_woke:, :pepe_smadge:, :pepe_despair:, :pepe_tremble:. They are public emotes from 7TV, each credited with its maker and terms in web/emoji/CREDITS.md, served by Allthing as WebP, at most 100 KB each.

  • The emoji picker shows the Pepe pack group by group, under headings: Classics, Reactions, Happy, Love, Sad and scared, Music and party, Hi and bye, Food and drink, Doing things, Dressed up and Signs. A bar above the grid has a button for each, its first Pepe for a face, that brings the group to the top; the group in view is marked as the grid scrolls. A search shows what it found, without groups, as before. React under a post has the groups too.

  • 76 more Pepe emoji, 55 of them animated: 179 in all, 137 animated. New are reactions (:pepe_facepalm:, :pepe_hmm:, :pepe_sus:, :pepe_yikes:, a weird clap, a thumbs up, W and L signs), moods (:pepe_hype:, :pepe_lmao:, a sigh, shaking nervously), music (guitar, piano, singing, a DJ), food (ice cream, a burger, noodles, sushi), Pepe at it (building in a hard hat, working at a desk, studying, swimming, sailing, painting) and Pepe dressed up (a ninja, a wizard casting a spell, a king, a chef, an angel). They are public emotes from 7TV, served by Allthing as WebP, at most 100 KB each. Each emoji now belongs to one of eleven groups, Classics first, and web/emoji/CREDITS.md gives each one's terms: why it may be here, by its source's rules. :pepe_pls: and :pepe_dance:, both "Pepe dances" before, now say which dance. A search finds the start of a word, in a name or a description, so "eat" finds the Pepes that eat but not the one that sweats, and "no" no longer finds the piano.

  • Show less, beside Favorite on a profile (anyone's but your own): you see less of the account, gently. In your hot and top feeds, on the front page and in channels, its casts weigh as if they had half their likes, recasts and replies, so they sit a little lower; they carry no mark, nothing is hidden, and new stays in time order. An account is a favorite or one you see less of, not both. It is kept in your browser and sent nowhere, like favorites.

  • Favorites. Favorite, at the top right of a profile (anyone's but your own; under the name on a phone), makes the account one of your favorites. In your hot and top feeds, on the front page and in channels, its casts weigh as if they had four times their likes, recasts and replies, so they rise, and they carry a small star; new stays in time order, and nothing is hidden. Favorites are kept in your browser and sent nowhere, so they need no sign-in, and your other tabs follow a change.

  • Favorite and Show less on profile cards. The card that opens from a name or a picture (on hover, or a tap on a phone) has the profile's two buttons, small, in a row between its counts and its facts, on anyone's card but your own. They are toggles, one or the other, and the profile's own: pressed in a card, they show pressed on the profile and in your other tabs, and a press is said to screen readers ("@alice's casts rise in your feeds"). A feed on screen keeps its order under the card, so the cast you're on stays put; the new order shows when the feed is next loaded, sorted or filtered, and a favorite's star at once. (A change made in another tab still re-sorts it in place.) And a profile card opened by the keyboard from a likes or recasts list as the list pops in no longer lands over the list when the pop-in ends: the list's pop-in, a transform that held the card, stops as the card opens beside it.

  • Scheduled casts. The clock in the composer, after the GIF button, picks when a cast, a reply or a quote goes out: a minute to 30 days ahead, in the reader's own time zone. The browser signs it at once with the reader's key, for that time, and the Worker keeps the signed cast in D1 and gives it to the node at its time, from the every-minute cron run; it never has the key, and the network refuses a message timestamped more than ten minutes ahead, so none can go out early. /scheduled, in the account menu, lists the casts waiting, with Edit (signed again, in its place) and Cancel, those that didn't go out and why, and those posted this past week. /api/v1/scheduled takes only a signed-in session's same-origin requests, and keeps 25 waiting per reader at most.

  • Drafts last, as in the Farcaster app. The composer keeps one for each reply, quote and cast (in a channel or on the front page) in your browser as you write, so starting another, closing it or reloading the page loses none, and each comes back where you left it. Closing says "Draft kept". They last 14 days, the 30 newest. Discard, in the bar, asks once and lets one go; a cast that goes out lets its draft go. The composer has the Farcaster app's Compose and Drafts tabs: Drafts lists yours, newest first ("Reply to @alice", its first two lines, how long ago), takes one up with what a reply answers, and deletes one.

  • Emoji reactions on casts. Farcaster's own reactions are only likes and recasts (the node refuses any other kind), so an emoji reaction is a reply that is one Pepe and nothing else, which every Farcaster app shows as a reply. React, after Reply and Quote under a post or a reply (its icon alone on a phone), opens the Pepe picker, emoji only; a pick goes out at once with the reader's key and says so, with a link to the reply. Under a post and each reply, the replies that are one Pepe gather as chips, one per emoji, with how many accounts reacted (each once) and who; the reader's own join them, pressed, and a chip reacts with its emoji too. The replies stay in the thread.

  • Animated Pepe stickers. A sticker button beside the emoji button opens 32 larger Pepes (128 px GIFs from 7TV, credited in web/stickers/CREDITS.md) in the picker's Stickers tab, your recently used ones first, searched as the emoji are. A sticker goes with the cast in the GIF's place, as an image embed of its GIF at allthing.social, so every Farcaster app shows it. Allthing shows its stickers large and frameless, apart from a cast's photos, and as stills with reduced motion.

  • A page of Farcaster's daily active accounts, /dau, after SnapMeter's dashboard. It shows the accounts active in the 24 hours to the last whole hour against the 24 before, today so far, yesterday and the past 30 days; the past month by UTC day, as bars and as a table, with partly counted days marked; what accounts did on the last whole day, by kind of message; accounts by app, naming the 20 apps with the most; and how they were counted, since when, and any gaps. While the count catches up after a deploy or an outage, the page says how far it has read, and today and yesterday follow the clock. An app's count of active accounts, on the client bar's cards, links to it, and the page's one request starts from the inline script. The static copy has no count and says where it is.

  • Each app's daily active accounts, on the front page's client bar. Pointing at an app's segment or name (tapping it on a phone, or resting keyboard focus on it) opens its card: how many accounts were active through it in the 24 hours to the last whole hour, its share of the trending casts, and a link that shows only its casts. An account's app is the one that requested the key it signed with, so an account that used two apps counts for each. The cron keeps each hour's signing keys and learns each key's app once, from the event stream for keys added onchain and otherwise by looking up the account's keys, 24 accounts a run. Once an hour D1 counts the past 24 hours' keys by app, exactly. After a deploy, the first count by app comes once 24 hours of keys are kept, and until every account's apps are known (about 10 hours of lookups) a card says what share is, and gives its count as a floor. /api/v1/activity serves the counts as apps_24h.

  • Farcaster's daily active accounts, counted as SnapMeter counts them: an account is active when one of its messages (a cast, reaction, follow, profile change and so on, on SnapMeter's closed list) was merged into the network, and it counts once in a window. A cron trigger every 2 minutes reads Hypersnap's event stream on from where it stopped and keeps each UTC hour's and day's active accounts as compact sets in D1, so the counts are exact. /api/v1/activity serves the 24 hours to the last whole hour and the 24 before, as SnapMeter's rolling window (t − 24 h, t], the 30 days to yesterday, and each day's count with its messages by kind, saying when counting began and any gaps. On 20 October 2026 Farcaster had 6,174 active accounts in 24.5 hours. The client usage bar and a page of their own will show them next.

  • An emoji picker in the composer. A Pepe beside the GIF button opens the Pepe pack: a grid that searches by name and description as you type (Enter picks the first), with your recently used emoji first (kept in this browser). Each emoji is still until pointed at or focused, and the foot of the picker shows the one pointed at with its shortcode, beside a link to the credits. A pick puts its :pepe_name: in your text at the caret, spaced from the words beside it. A click closes the picker (Shift keeps it open), and on a phone a tap leaves it open for another, without the keyboard. While a GIF or emoji picker is open, the text box shrinks to two lines so both fit.

  • Pepe emoji in casts. :pepe_name:, in any case, shows one of 103 Pepe emoji, 82 of them animated: Pepe holding up a NO sign (:pepe_no:), smug, blushing, hugging a heart, sitting, and many more. They are public emotes from 7TV, FrankerFaceZ and emoji.gg, each credited with its source and maker in web/emoji/CREDITS.md, and Allthing serves its own copies as WebP (64 px tall, at most 150 KB each), loaded lazily. A cast of one to six emoji and nothing else shows them large; with reduced motion they stand still. A shortcode that isn't in the pack stays text, and other Farcaster apps show the shortcode as written.

  • Following, unfollowing and deleting one's own casts from Allthing, with the key a reader approved for liking. On anyone else's profile, Follow sits beside the name once Hypersnap has said whether the reader follows the account (one read from the browser per profile view); hovering Following offers Unfollow, and the follower count follows along. Under the reader's own post and replies, Delete asks once more in place ("Delete this cast?", Keep focused), then takes the cast back; it reads "You deleted this cast." wherever it shows in that tab, after a reload too, since cached pages may carry it for a while. Each is signed in the browser and handed to Hypersnap, as a like is; the encodings were checked against Hypersnap, which refused them only for the throwaway key's signer.

  • "Cast your reply" under a post, as under a cast in the Farcaster app's conversations. Signed in, a row with your picture, the faint words and a dimmed Reply sits between the post and its replies; a click, a tap, Enter or Space opens Reply for the post. It's there as the post first shows, so nothing moves when it comes.

  • Followers and following. A profile's follower and following counts are links, to /u/<name>/followers and /u/<name>/following (pointing at one, or pressing it, starts reading the list), and tabs switch between the two. The people come newest follow first, as the Farcaster app lists them: each row has their picture, name and handle, the start of their bio and how many follow them; it opens their profile, and their name shows their profile card. A list reads on as you scroll, a page of up to 140 people at a time (/api/v1/follows), and only the rows in view and a few either side are drawn, so thousands of people scroll as a screenful does; Back from a profile puts the list back where it was. The node keeps follows in two shards that don't page in step: the Worker puts each page in time order across them, carrying in its cursor the links a later page could still come before, and reads nothing more of a shard that has ended, which the node would start over. The static copy has them too. The search box and sort above the list are in place, not working yet. Also fixed: a page whose code was still coming when the reader went back no longer takes the place of the page they went back to.

  • Casting, replying and quoting from Allthing, with the key a reader approved for liking and recasting. The composer's Cast, Reply or Quote (new, beside Reply under a post and its replies) posts from the browser: "@name"s that belong to someone become mentions, looked up at Hypersnap; the GIF and the quoted cast are embedded; a reply answers its cast and a cast on a channel page goes to that channel. Over 320 bytes it is a long cast, up to 1,024 bytes, counted as the text nears it. A cast that goes out closes the composer with a notice that links to it; one the network refuses keeps the draft and says why. A reader with no key yet approves one first. The composer offers no way round to the Farcaster app; only where the Worker endorses no keys does the draft open there instead.

  • Liking and recasting from Allthing, for a signed-in reader. The first like or recast opens a dialog with a QR code to scan with the phone's camera and approve in the Farcaster app, which adds an app key to the reader's account. The key is made by the reader's browser (WebCrypto Ed25519, not exportable) and kept there (IndexedDB); Allthing's servers never see it. The Worker only endorses it: a SignedKeyRequest signed with the custody key of Allthing's own Farcaster account (/api/v1/auth/signer), which the Farcaster app shows as Allthing's request; the signature was checked against the validator's own validate() on OP Mainnet. From then on, the heart and recast icons under every cast like and recast, and undo it: the browser signs each message and hands it to Hypersnap. The button and count change at once and change back, with a notice, if the network refuses; a key removed in the Farcaster app is forgotten, and the next like asks for a new one. Casts the reader liked or recasted show it, from their latest 100 of each and, on a post page, that cast. Signing out, or "Stop liking from this browser" in the account menu, deletes the key. It needs the ALLTHING_FID and ALLTHING_CUSTODY_KEY Worker secrets (ALLTHING_SPONSOR=1 pays readers' approval fees from the account's warps; docs/DEPLOY.md); without them a signed-in reader's heart and recast icons are pictures, and /api/v1/status says which (writes).

  • Writing a cast or a reply, with a GIF, for a signed-in reader. Cast, at the top right of the front page's feed and of a channel's, starts a cast, in the channel on a channel page: a pill in Allthing's indigo accent turning to the portal cyan, lettered in the wordmark's serif, and on a phone a round button floating at the bottom right of those feeds, with notices above it; Reply under a post or a reply starts a reply. Where Allthing doesn't post it with the reader's key (above), Post in Farcaster opens the draft in the Farcaster app (farcaster.xyz's compose link), where the reader posts it. The GIF button opens a picker of KLIPY's trending GIFs, with a "Search KLIPY" box that searches once typing pauses; the browser asks KLIPY directly, as KLIPY requires, with a key the Worker hands signed-in readers (/api/v1/gifs/key). It needs the KLIPY_API_KEY Worker secret (docs/DEPLOY.md); without it there is no GIF button. /api/v1/status says whether it is set (gifs.keyed).

  • Signing in recovers by itself. A code that runs out while the dialog is open is replaced, twice, before the dialog offers a new one. When the Worker can't check an approval just now (Optimism can't be read), the same approval is checked again after 1, 2 and 4 seconds and then on "Try again", with no new code to scan. A hidden tab stops asking Farcaster's relay and asks at once when it is back in view. The dialog shows who approved while the Worker checks, the signed message lapses with the sign-in's nonce on the Worker's clock, and signing in or out in one tab shows in the others. Adapted from Warpkeep's sign-in.

  • Sign in with Farcaster, by QR code: "Sign in" in the header opens a dialog with a QR code, and nothing else to choose, to scan with the phone's camera; the person approves in the Farcaster app. The Worker checks the signed message as Farcaster's auth client does (the nonce in a cookie bound to the browser, this site's domain, the signature's signer, and on OP Mainnet that the signer is the account's custody or auth address), then keeps the session in an HMAC-signed, HttpOnly, Secure cookie for 30 days. The header shows the account's picture: a click opens their profile, and hovering it (keyboard focus resting on it, or a tap on a touch screen) shows a menu with their profile and "Sign out". It is the only way to sign in, it posts nothing, and it needs the SESSION_SECRET Worker secret (docs/DEPLOY.md). The static copy has no sign-in.

  • A speed report: e2e/speed-report.spec.ts, run by CI's speed report job on a manual run, loads the front page of allthing.social cold seven times at 1280 px (10 Mbps) and at 390 px (slow 4G, CPU slowed 4×), and prints the medians of time to first byte, WebAssembly and data arrival, first cast, LCP and the slowest interaction. See docs/PERFORMANCE.md.

  • Names in a Liked by or Recasted by list open their profile card beside the list on hover, keyboard focus or tap, as names elsewhere do; the list stays open, and a tap opens the card instead of the profile.

  • /api/v1/status says whether Solana wallet counts have a keyed RPC endpoint (solana_rpc.keyed, and the endpoint's host, never its key), so a deploy can be checked after setting the SOLANA_RPC_URL secret.

  • The usage bar filters the feed: each app is a link to its casts, the filter is a removable chip, and "Other apps" lists the smaller clients. The filter stays in the URL with the sort and time range.

  • API answers carry a Server-Timing header with the Worker's time for them, named after how they were served (fresh, revalidating, miss or stale-if-error), so slow page builds can be measured in production from any browser.

  • Production performance checks wait for the intended main commit to deploy and fail clearly if it does not arrive, instead of benchmarking the previous release.

Changed

  • Messages is one simple inbox on SpacetimeDB: Allthing's public #general room and your end-to-end encrypted conversations in one list, with the one open beside it (or in its place on a phone), a composer, and New message to start one. No tabs, and no API key to paste. The URLs are /messages?room=general, /messages?to=<fid> and /messages?new=1. The encryption and the Worker's attestation are unchanged.

  • The front page's time range offers Past hour, 6 hours, 12 hours, 24 hours, 7 days, 30 days and All time, in place of Past hour, Today, Past week, Past month, Past year and All time. Past year leaves the menu; a link that names it still shows it. Six hours is one of Hypersnap's trending windows; 12 hours is cut from its 24-hour list.

  • Cast stays in reach as you scroll, and covers nothing. On a computer it's in the site header, before your picture, on the front page, a channel and your own profile, so it's always in view; the feed's head, a channel's title and your profile's header no longer have one, and on other pages its place in the header is kept, unseen, so the search box doesn't move. On a phone the round button is on your own profile too, smaller (48 px), closer to the corner and with a softer shadow; those pages leave room after the footer, so the last card's like, recast and reply and the footer's links scroll clear of it, and it steps aside while a dialog, the lightbox or a popover is open, or the keyboard is up.

  • The feed is denser, as in the Farcaster app. In a list of casts (the front page, new, channels, profiles and search, and the casts they quote), an image or a video is at most 270 px tall on a computer and half the screen on a phone, where it was up to 70% of the window tall (80% when portrait). Each keeps its own shape, so a tall picture shows whole and narrower; a cast's images are one row of squares on a computer, and a YouTube, Vimeo, Loom or Streamable player is no taller than the rest. A quoted cast's media stays at most 320 px on a phone, and a GIF is no taller than a list's media. A click or a tap on an image opens it in the media viewer at full size, where it opened the post; a video plays in place with its controls, and its corner button opens it in the viewer. A post's page keeps its large media, for the cast it opens and its replies. On the same casts, a screen holds more of the front page: 1.20 → 1.82 cards at 1280 px for today's hot casts and 1.53 → 2.21 for the past week's, and 1.33 → 1.36 and 1.69 → 1.75 at 390 px, where only tall media was taller than half the screen before.

  • The README says what Allthing does now: one approval in the Farcaster app signs a reader in and lets them write; casts carry images and video; the composer is a sheet on a phone; /dau reads like SnapMeter's dashboard; and the architecture sketch shows attachments served from R2 and writes signed in the browser and sent to Hypersnap.

  • Your reply shows in the thread at once, as in the Farcaster app. Reply closes the composer straight away, and the reply shows on the post's page under what it answers, at the top of its replies (under a reply for a reply to it), with your picture, name, text, images and videos, as posting: faded a little, "Posting…" in its time's place, its buttons doing nothing yet. The post's reply count, there and on feed cards, counts it at once. Once Hypersnap's store shows it, it's a reply like the others, with its time, which links to it, and its buttons, and screen readers hear "Reply posted."; once the page lists it, the page's own shows instead, once. If the node refuses it, can't be reached, or doesn't show it in 15 seconds, it goes, the count goes back, and the composer opens again with your draft and why; Retry, there while the draft is unchanged, sends the same signed message again (the network keeps it once). A reply you hand to the Farcaster app shows in the thread the same way, "Posting in Farcaster" until it's there. e2e/reply-in-thread.fixture.spec.ts runs each at 1280 and 390 px.

  • Writes from Allthing stand only once Farcaster has them. A like, a recast, a quote (or a cast, a reply, a follow, a delete, a Pepe reaction) and the undoing of each is signed in your browser with the key you approved for @allthingapp and handed to Hypersnap, which passes it to Snapchain's validators; the node taking it isn't the network merging it, so your browser now reads it back from the node until its store shows it: within about 6 seconds as the network runs (measured on 21 October 2026), and it looks for 15 before giving up, so a slow moment doesn't undo a like that landed. Until then the heart or the recast icon and its count pulse, and a quote shows above your feed and on your profile as "Posting…"; once the node shows it, it stands ("Posted. View"). If the node refuses it, can't be reached (the same message goes again after 1, 2 and 4 seconds first), or doesn't show it in time, it's taken back exactly: the button and the count as they were, the quote gone from your lists with its draft back in the composer, and a notice says why, with Retry (a quote goes again as the same message, which the network keeps once). A key the node no longer knows offers Approve again. A message the node already has, as when its answer was lost, counts once. Hypersnap's answers when it's busy (400s whose status says Unavailable) now count as busy, for scheduled casts too. e2e/verified-writes.fixture.spec.ts runs each outcome at 1280 and 390 px against a stand-in node that checks every message's hash and signature as Snapchain does, and that every spec that writes now uses. No other public node takes a browser's messages without a key, so Hypersnap stays the one written to.

  • A feed card's replies popover opens with its replies at once, hovered on a computer or tapped on a phone: the batches that preload who liked and recasted the cards in view and a screen ahead bring their replies too, for cards with 1 to 100 replies, so the popover asks for nothing. Each is what the popover read before (/api/v1/replies): the ten most engaged direct replies, with spam and mini apps left out. A batch is still one Worker request; it reads a conversation per card besides its lists, 18 reads and 20 subrequests at most (26 with its replies' link pages, against a budget of 45), and parses its replies only in what its lists leave of its share of CPU, the smallest conversations first, so a batch with replies takes no more CPU than one of full lists did (about 3 ms natively). A card whose replies didn't come loads them when its popover opens, as before, or from its post when hovering its card prefetched it.

  • The smoothness check times how much of a CPU the run gets, before each interaction, and away from CI skips its timed budgets (INP, long tasks) on a machine too busy to time them, saying why, and still reports their numbers. On a 4-core machine shared with other builds at load 9, a thread got 0.4 to 0.6 of a CPU, and the phone's sort tab, 40 to 56 ms when the budgets were calibrated, took 104 ms. SMOOTH_STRICT=1 judges them anyway; CI always does.

  • The smoothness check holds scrolling to a budget (in a 6,000 px scroll, the 95th percentile of frame intervals at most 20 ms and at most 5% of frames dropped; the feed keeps 0 to 4 of about 150 dropped), and times the composer's emoji and sticker pickers and a key typed in a draft. A post's replies past its first screen come a frame after the post, so the post shows sooner.

  • Attachments are on at allthing.social: R2 is enabled on the account, and wrangler.toml binds the bucket allthing-media as MEDIA, so /api/v1/status says media.ready: true, signed-in readers can upload images and videos (POST /api/v1/media), and the Worker serves them at /m/. wrangler dev gives the binding a local R2; the attachments test also runs a Worker without it, which still answers 404.

  • An app's card on the client bar says how its active accounts are counted, and /dau reads like SnapMeter's dashboard. Under the card's count, one line: any message (a cast, reaction, follow…) signed with a key the app requested counts its account once, from Hypersnap's events, and a link to /dau. On /dau, SnapMeter's meter comes first: the past 24 hours and their change on the 24 before, as a count and a percent; today so far beside them; whether the count is up to date; readouts of yesterday, the past 30 days and the 7-day average against the 7 days before; and today against the month's median day. The history has SnapMeter's ranges: the past 48 hours by the hour (the Worker now serves each hour's count, the length of a set it already reads), or 7 or 30 days by UTC day. Its bars are thinner, on one value axis of round steps, and a bar's readout stays on the plot. Each range has a table. What accounts did is a bar for each kind, and how they're counted is SnapMeter's definitions, term by term. The page's code, fetched with its counts, is 6.7 KB larger at brotli 11.

  • Who liked and recasted feed cards loads a run of six cards ahead of the reader: the next run's batch comes once its first card is less than a screen below the view, so its popovers already have their people when the reader gets there, on a computer and on a phone. A page view preloads up to 48 cards (eight batches), where it stopped at 24, so the front page's 25th card and most of its second page open as fast as its first. A batch is still one Worker request, one at a time and at least 2 s apart, and a run scrolled past before its turn isn't loaded. Capacity is about 40,000 page views a day by BUDGET.md's mix, from about 43,000.

  • Show less and Favorite move an account's casts a long way, where they moved them a little. In your hot and top feeds, on the front page and in channels, the casts of an account you see less of weigh a 25th of their likes, recasts and replies (they weighed half), which in hot is as if posted 17½ hours earlier for each day the list spans; and in new, where they stayed in time order, they're placed as if posted 6 hours earlier for each day the list spans, though the time they show is the real one. A favorite's casts weigh 25 times theirs (four times before), as if 17½ hours newer for each day in hot, and new keeps them in time order. Counting the days a list spans, from its oldest cast to its newest and a day at least, moves a cast as far through a quiet channel's week of casts, or the past week's front page, as through a day's: over a week hot is nearly time order, where 17½ hours would move a cast only a little. In top the weight now applies to a cast's points plus one, so a cast nobody has reacted to yet moves too. Measured on 120 casts over a day, a typical account's six casts, at a median place of 61.5 in hot and 59 in top, go to 117 and 117.5 when you see less of it (75 and 93.5 with half), and to 4.5 and 3.5 as a favorite (27 and 10 with four times); in new, from 58.5 to 87. On the same casts over a week, hot goes from 59.5 to 117.5 and 3.5 (73 and 50 with a fixed 17½ hours), top as over a day, and new from 58.5 to 86.5. Nothing is hidden: every cast is still in the list. The hints say so: "Considerably reduces their casts in your feeds: well down in hot and top, lower in new. Nothing is hidden. Kept in this browser only." and "Considerably boosts their casts in your hot and top feeds. Kept in this browser only." The front page's first cards, drawn before the app has loaded, follow the same order.

  • Signing in is one step where Allthing can write for you, as on allthing.social. "Sign in" asks you to approve Allthing's key in the Farcaster app, as @allthingapp, and that one approval signs you in and lets you post, like and recast from here ("via Allthing"): there is no Sign In With Farcaster first and no second approval after. On a computer the dialog shows a QR code for your phone; on a phone, Approve in Farcaster opens the app. Your first like goes out at once with the key, which stays in this browser. Where the Worker endorses no keys, signing in is Sign In With Farcaster by QR code, as before.

  • The front page shows its first casts before the app has loaded: about 1.2 s into a visit on a slow phone instead of 3.4 s, and 0.26 s instead of 0.64 s on a computer, throttled as the speed report's profiles. The page's data comes well before the app's WebAssembly, so a small script (web/paint.js, 10 KB at brotli) draws the cards the app draws first from it, in the app's order and with its markup, and the app's own cards take their places once they're drawn, in the same boxes, without coming in again. Until then they can't be used (a tap does nothing, and screen readers wait for the app's), and the app's own come as before on a computer and 66 ms later on the phone. A single image's tile has its final box before the image comes, from Cloudflare Images' description of its size (about 110 bytes), and the app's tile takes the same box, so the first screen no longer moves as its images come: layout shift 0.000 in every run, where the phone's was 0.155 (median of 9). The images wait for the WebAssembly, so as not to share a slow line with it. Only the front page's hot and top over an hour, a day or a week are drawn early; other pages, and cards the app draws otherwise (a quote, a video, a sticker), show as before. The speed report times the first cast on screen and the app's own apart, and the production performance check waits for the app's own cards.

  • A post's page, a profile's, a channel's, search's and the channels' are code of their own too. The app every visit downloads is 51.9 KB (10.2%) smaller again at brotli 11. Their code comes before it's needed: a post's a few seconds after the app starts and as a card is pointed at or touched, and the others' as a link to them is pointed at, touched or focused. The phone's first cast comes about 260 ms sooner. A visit that starts on one of these pages fetches its code alongside the app.

  • Writing is code of its own too: signing and sending a like, a recast, a follow, a delete or a cast, approving a key, and looking on the node for what was handed to the Farcaster app. A signed-in reader's browser fetches it as soon as the page knows who they are; a signed-out reader's never does. The app every visit downloads is 14.2 KB (2.7%) smaller again at brotli 11. If writing's code can't be fetched, a signed-in reader's writes go to the Farcaster app.

  • Signing in and the list of scheduled casts are code of their own too, fetched as Sign in is pointed at, focused or pressed, and when /scheduled opens: the app every visit downloads is 15.0 KB (2.9%) smaller again at brotli 11.

  • The composer and React's Pepe picker are code of their own too, so the app every visit downloads is 34.5 KB (6.2%) smaller again at brotli 11. The composer still opens at once, its text box ready to type in the first time, while its pickers, bar and posting come; their code starts coming as Cast, Reply or Quote is pointed at, focused or pressed, so it's usually there before the click. React's picker comes the same way. If a part like these can't be fetched, it says so in its place with Reload, and the page doesn't reload by itself, so a draft isn't lost.

  • The composer's emoji picker answers at once. It draws the groups in view as it opens and the rest one a frame after, and an emoji's picture loads as it comes into view, where it drew all 219 and loaded all their pictures in the click that opened it: on the phone it opens in 72 ms (128 to 192 before), on a computer in 80 (264 to 408), with no long task. A dialog's backdrop is dimmed, not blurred, as a sheet is on iOS: the blur was drawn again over the whole screen in every frame anything in the dialog moved.

  • The app downloads less before it shows anything: the DAU page's dashboard is code of its own, fetched with the page's counts the first time /dau opens, instead of part of what every visit downloads (6.6 KB, 1.2%, less at brotli 11). The build cuts such parts out with wasm-split (cargo xtask, crate::split in allthing-web; docs/PERFORMANCE.md). A page left open over a deploy, whose parts are then gone, loads again once as the new version; if a part still can't be fetched, it says so and offers to try again.

  • The Worker uploads at half the size. Its WebAssembly no longer carries the relocations the workspace's --emit-relocs leaves for the browser app's wasm-split (linking and reloc.*, more than half its bytes), which nothing reads there: cargo xtask strip-worker drops them after worker-build. wrangler deploy --dry-run says 2,568 KiB (794 KiB gzip) where it said 5,443 KiB (1,403 KiB gzip), against the Free plan's 3 MB compressed limit, and a fresh Worker answered its first request under wrangler dev in 822 ms where it took 1,123 ms. Its function names stay, so stack traces in Cloudflare's logs stay readable.

  • On a phone a sort tab answers at once. The tab tapped shows as chosen in the next frame, and the feed follows once that frame has painted. Switching between hot and new took 144 ms to answer with the CPU slowed 4×, as the speed report measures it, and now takes 48 ms. The new feed also no longer redraws its cards as it's left.

  • Back to the front page is instant. The front page is kept while you read a post or a profile and put back as it was, its cards where they were and each the height it had, instead of being drawn again, which took 110 to 260 ms of the moment after Back on the smoothness spec's phone (390 px, the CPU slowed 2×); opening a post from it no longer tears it down either. Kept for over 5 minutes, it's drawn anew.

  • On /dau, a day's count and date show above its bar when the bar is pointed at, tapped, or reached with the arrow keys once the chart has keyboard focus, in place of the browser's tooltip, which came late and never on a phone. A bar's whole column is its target, so a short bar is as easy to hit, and the table still lists every day.

  • A profile card's wallet legend adds up to 100. Past its two largest chains, it says how many more there are and their share together ("+4 35%"), where it said only "+4". The profile says what the shares are of ("Share of the verified wallets' transactions, by chain."), and "?" beside Wallet activity, on the profile and the card, links to how they're counted (docs/WALLETS.md), which now gives both of Solana's caps.

  • Allthing asks for readers' keys as its own account, @allthingapp (FID 3356318, ALLTHING_FID in wrangler.toml), and shows the casts written with them "via Allthing". Writing from Allthing switches on once that account's custody key is set as the Worker's ALLTHING_CUSTODY_KEY secret, in Cloudflare only (DEPLOY.md); until then every write goes to the Farcaster app, as now. On a phone, which can't scan its own screen, the approval opens the Farcaster app from a button instead of showing a QR code. /api/v1/status names the address of the custody key set (writes.address), never the key, so a deploy can be checked against the account's.

  • Revoke permissions, in the account menu where "Stop liking from this browser" was, takes a second click: the first arms it ("Click again to revoke", or "Tap again to revoke" on a phone), and a second within 5 seconds revokes; it disarms after those 5 seconds, on Escape, when the pointer or focus moves away, and as the menu closes. What it does is said under it, in Allthing's tooltip look, on hover, keyboard focus and once armed, phones included: writing from this browser stops until you approve Allthing again, you stay signed in, casts you scheduled still go out, and Allthing's key stays among your Farcaster app's connected apps until you remove it there, as nothing changes on chain. A notice then says "Write permission revoked in this browser." e2e/revoke-permissions.fixture.spec.ts checks it at 1280 and 390 px, by mouse, touch and keyboard, with axe.

  • The sound badge on an autoplaying video ("Sound off · tap for sound") is hidden until wanted. On a computer it shows while the pointer is over the video, or keyboard focus is on it or its play layer, so a video at rest is just the video. On a phone, which can't hover, a tap on the video shows it for 3 seconds instead of opening the controls: a tap on the badge then turns the sound on or off, and a tap on the video while it shows plays the video with sound and controls, as a tap did before. The badge's target is 44 px tall on a phone; a screen reader or a key plays the video at once, as before.

  • The composer no longer says "Allthing doesn't post: this opens the Farcaster app with your cast ready to post.", and the dialog that hands a like, a recast, a follow or a delete to the Farcaster app no longer says "Allthing doesn't act for you: you do this in the Farcaster app, as yourself." Their buttons and titles already say where each goes (Post in Farcaster, Like in Farcaster). /scheduled with nothing waiting says only "Nothing scheduled."

  • The heart and recast icons under a cast never open the Liked by or Recasted by list: they like and recast (in the Farcaster app, as above). Pointing at an icon or its count previews who, as before; the preview's "See everyone", or a click on the count, opens the full list. On a phone, a tap on the count, which has a target of its own beside the icon's, previews who.

  • On a phone, a tapped profile picture or likes count opens complete, as a hovered one does with a mouse. Who liked and recasted the cards in view now loads ahead on touch screens too. So do the vote and wallet activity of the authors of cards the reader stops at, up to 6 authors per 5 minutes. Before, a tapped card showed placeholders for 130–350 ms, and a tapped count waited as long for its list. It costs a phone's feed view about 4 to 5 more requests (docs/BUDGET.md).

  • A profile card opened from a Liked by, Recasted by or Replies preview is complete from the frame it shows. Once the pointer or keyboard focus comes into the preview's list (on a touch screen, once a tap opens it), the vote and wallet activity of the people it names load, in the list's order, two at a time and at most 20 people per 5 minutes. A card whose vote or wallets are still on their way waits for them out of sight, for half a second at most, instead of showing them loading and growing when they come.

  • A profile, feed or post that isn't cached yet builds faster: it checks at most six unknown links for mini apps per build, in one parallel batch, instead of twelve in two batches. The second batch waited for the first's slowest page; on cold profiles it took 1.2 to 1.8 s of 1.6 to 2.6 s. Links left unchecked keep their casts listed until the next refresh checks them.

  • A profile that isn't cached yet builds faster again: its quoted casts and app names are read while its links are checked for mini apps, instead of after the check. On 19 October 2026 the check took up to 1.4 s of an uncached profile's build, and the reads after it another 0.1 to 0.2 s. What only a hidden cast needed (its quotes, its origin) is left out of the payload, as before.

  • Links to block explorers (Etherscan, Basescan, Arbiscan, Polygonscan, Celoscan, BscScan, Uniscan, Blockscout, Solscan and Solana Explorer) are no longer read to check for mini apps: their pages never declare one. A single Basescan link held an uncached profile's build for the whole 2.5 s link-page timeout.

  • A link page read to check for a mini app is read only up to the end of its <head>, where the meta tags are, instead of up to 128 KB: a large page from a slow server spent much of the 2.5 s the read is allowed on a body that is never looked at.

  • Nothing on screen calls a post or an account spam. A hidden reply says "Post hidden", a hidden quoted cast "Quoted cast hidden", and a filtered post opened by its link says which kinds of links Allthing leaves out. What is left out is unchanged; mini app posts still say they are mini apps.

  • Solana wallet counts are real when the SOLANA_RPC_URL secret is set: each verified Solana address is paged through its history (up to 5,000 signatures, shown as "5,000+"), from a keyed RPC endpoint. Without the secret, a Solana read that fails says it couldn't be read just now, instead of blaming the public RPC. See the Secrets section of docs/DEPLOY.md.

  • A quote of a quote shows inline, up to three levels: a quoted cast that quotes another shows that quote as a card, and so on. A quote beyond the third is a link, as before. Each level costs one bulk read in a listing's build, made while the build has budget to spare.

  • The Wallets button on a profile opens its list of verified addresses on hover or keyboard focus, on screens that hover, as the other popovers do. A tap or click still toggles it.

  • Wallet activity by chain sits in the profile header, under the joined date and NS vote, instead of a sidebar card. It is the same chart and Wallets list, at the same width as the rest of the profile on phones.

  • Opening a post, a profile or a channel directly (from a link elsewhere, or a reload) starts its data request as soon as the page arrives, alongside the app's download, as the front page already did. Before, it waited for the app to boot, about 450 ms on production. It is still one API call.

  • Opening a post, a profile or a channel answers the tap at once. The link or card shows pressed in the next frame, and the page follows once that frame is painted, not in the tap's own task: on the smoothness spec's phone (390 px, the CPU slowed 2×) a tap that opens a post now answers in 48 to 80 ms, where drawing the post in the tap's task took 136 to 216 ms. A page Allthing already has something of (a post or a profile a feed showed) opens then, and the placeholders of its replies or casts show only if those take longer than 250 ms; one it has nothing of yet (a channel, or a profile only linked to) keeps the page you're on, the link held pressed, until its data (and the page's code, the first time) comes, 250 ms at most, so it opens complete. A link to a post or a channel fetches its page's data as it's pressed or touched, or once the mouse rests on it for 100 ms.

  • A profile opened from a feed shows its header at once: the picture, names and fid from the casts the page showed, and the start of the bio, the follower counts and the join date from the profile card its payload carried. The casts load behind it, as a post's replies do. Where nothing was known about the account's bio and counts, they show placeholders, and a join date the profile would correct (an account from before November 2023) waits for the profile. Before, the page stayed a grey placeholder until the whole profile arrived, which took seconds when it wasn't cached.

  • Profile-card vote and wallet prefetches resume when foreground reads finish or fail, so navigating with pending profile facts no longer leaves the next feed's cards waiting for another interaction.

  • Sort tabs answer at once, and scrolling stays smooth as cards are added. A list rendered anew (a sort tab, another page) draws a screenful of cards in the task that asked for it and the rest a screenful at a time after each paint; a re-sort starts again from the top, and infinite scroll adds its 25 cards the same way. Before, the 25 cards were built inside the click (13–39 ms of WebAssembly on a fast desktop CPU). Back and forward still draw the whole list at once while they restore the reader's place.

  • The app's WebAssembly downloads a tenth smaller again: 393 KB instead of 441 KB. Its views are built with Leptos's type-erased components (--cfg erase_components), which boxes each reactive closure instead of generating view code for each. That costs 3–6 ms more to draw the first cards on a fast CPU, and saves about 240 ms of download on a slow 4G link.

  • The app's WebAssembly downloads a fifth smaller: about 434 KB instead of about 550 KB. The build compresses it at brotli's highest level, and the Worker serves that copy at /wasm/ to browsers that accept brotli; the edge's own on-the-fly compression is faster but larger. If the Worker can't answer (its daily allowance spent, say), the page loads the static copy as before.

  • Cancelling a scheduled cast is instant. On /scheduled, Yes, cancel it (or Yes, remove it, for one that didn't go out) takes the cast off the list at once, in a short fold (none with reduced motion), and its group's count goes down with it, while the server is asked; once it has done it, a notice says "Scheduled cast cancelled". If it can't, the cast comes back where it was, with why, and focus on it; if the cast had already gone out, it shows as posted, with View cast, not as an error. The question opens on Keep it, which, like Escape, puts focus back on Cancel; on a phone its answers share a row, and the list's buttons take a 44 px tap. With nothing left waiting, the list says where the composer's clock is, and Schedule a cast opens the composer with its clock on. The account menu's Scheduled casts says how many are waiting, as far as this browser knows, without asking the server. POST /api/v1/scheduled/cancel says what it did: cancelled (the same when asked twice), posted or sending with the cast's hash, or 403 for another account's cast. A cast the cron is giving the node can't be cancelled or changed in that moment: before, one cancelled then was said to be cancelled, and went out.

Fixed

  • The front page's first cards are drawn before the app again. web/paint.js stopped at a card that quotes a cast, and the front page usually has one among its first, so none were drawn and the first cast waited for the WebAssembly (4.6 s on production's phone profile, with the data in by 2.2 s). Quotes are now drawn as the app draws them, nested quotes, their images' boxes and links to quotes not carried included, and on a phone the early cards stop at the fourth, where the app's poll comes, so nothing moves when the app takes over. Locally, the first cast came at 1.6 s instead of 5.5 s on the phone profile, and at 0.45 s instead of 1.2 s on the desktop's.

  • The front page's "Past hour" showed casts hours old. Hypersnap's list and a copy the edge kept for a day when a rebuild failed both reached past their window. Each window now keeps only the casts posted inside it, judged by each cast's own time before the page is cut, and the page drops what has aged out since it was built. A copy is kept no longer than its window. A range whose list has run out says how many casts it holds ("Only 3 casts in the past 12 hours.") instead of padding the list with older ones.

  • The composer opens without holding up a phone. Cast, Reply and Quote still open it with the box focused and its bar in the first frame, but what goes under the text (a GIF, the pickers, when it goes out) comes in the next frame when the draft has none, the bar asks for the GIF key and whether files can go once it shows, keeping what it was told last, and the header's line and sheen stand still from the frame after a dialog opens. With the CPU slowed 2×, the tap's work went from 46–59 ms to 36–45 ms, and its frame is under 50 ms again (the smoothness check failed on main with 51–56 ms).

  • Likes, recasts and replies on posts and feed cards are as current as notifications, or more. Before, a post's counts came from its read, fresh at the edge for 2 minutes and kept 5 by the app, and never changed while it was open, while the bell found the same like within 1 to 3 minutes. Now the counts of the cards and the post in view are read again every 30 s while the reader is active (/api/v1/counts: one bulk Hypersnap read for up to 12 casts, fresh 15 s at the edge), and change in place without undoing the reader's own likes, recasts or replies waiting to land. A post (/api/v1/cast) and replies previews are fresh for 30 s. New replies on an open post show as a "N new replies" button over the page, which brings them in when pressed, so nothing moves under the reader. See docs/BUDGET.md for the cost: about 30,000 page views a day, where it was 40,000.

  • Videos attached in Allthing play in the Farcaster app. It plays only HLS (.m3u8) video in a cast, wherever it is hosted, and showed an MP4 at allthing.social/m/… as "No preview found for shared link", however it was served: a live MP4 on another host with the same headers fares the same. Now the composer remuxes an MP4 or MOV (H.264 or HEVC, with or without AAC) in the browser before it uploads it: the same frames and sound, nothing re-encoded, as one fragmented MP4 cut at key frames every 4 s or more, without the file's metadata (where it was recorded, the phone). It is kept as <id>.mp4, and the cast embeds https://allthing.social/m/<fid>/<id>.m3u8, a master playlist with the video's size and codecs, whose media playlist (<id>.v.m3u8) names byte ranges of the MP4. The Worker writes both from the index the browser sent with the upload, kept as the file's R2 metadata: one R2 head a playlist, no body read. Allthing plays the MP4 itself. A video that can't be remuxed (WebM, other codecs, an MP4 already fragmented) is kept and embedded as before. /m/ also answers CORS preflights (OPTIONS, 204) and lets other sites' players read Content-Range. Casts already posted keep the URL they embed: an MP4 posted before this still shows as a link there.

  • Feed refreshes retain all three levels of already loaded quotes. They no longer reread known nested quotes or collapse them to links when the provider is unavailable or the request budget is tight.

  • Keyed Solana RPC failures keep the private endpoint and provider content out of public wallet error responses, while preserving status, timeout and request-budget diagnostics.

  • An image a cast shows alone, when it is tiny (an icon, a 1 × 1 pixel), is a box at least 48 px wide, so a press on it opens the media viewer. It was drawn at 1.25 times its own width, so a 1 × 1 image was a 1.25 px box that a click could miss by rounding; this broke the "long words are cut short" lightbox caption check at 1280 px.

  • An image a cast shows alone has its box before it comes, so nothing moves when it does. A post loaded cold showed its replies, then pushed them most of a screen down as its image came; another time range or the new tab pushed the front page's cards down 300 to 450 px at a time, and so did scrolling. The browser now asks Cloudflare Images for each such image's size (about 110 bytes, straight from the browser, once a visit per image, six at a time; never through the Worker), or, for an image hosted elsewhere that is about to show, reads it from the image's first bytes. A list draws its next screenful of cards once their images have their sizes, where it ends in view, and a post's page, and a channel's with a pinned cast, once the images at their top do, keeping their placeholders meanwhile, for a second at most; an image whose size is still unknown then holds a square until it comes. Layout shift as Chrome counts it, on allthing.social before and locally with live data after: a post loaded cold 0.25 → 0 at 1280 px and 0.30 → 0 at 390 px (0.32 and 0.39 before, counting the footer's own move as the page fills, which is the page's height while loading and still 0.07 at 1280 px); Past hour 0.49 → 0.0003 and 0.50 → 0.006; Past week 0.31 → 0 and 0.33 → 0.0001; the new tab 0.13 → 0 at 1280 px; 14 scrolls down the front page 0.19 → 0.001 at 1280 px and 0.04 → 0 at 390 px.

  • On a phone, the smallest targets take a fingertip too. A reply's collapse toggle (22 px), the "?" beside a profile's NS vote and wallet activity (14 px) and the apps under the front page's bar (19 px tall) each take a tap in a band 44 px tall, centred on them, as the other small targets do; the "?"'s is 36 px wide. No band meets another target's: on a touch screen a reply has 8 px more room above it, clear of the actions of the reply before it or of the one it answers, and the apps' rows are 45 px apart (24 before), the first 19 px under the bar (7 before), whose segments keep their own taps. With a mouse, nothing changes.

  • Long names and long words no longer break a card. On a phone, an author with a long name and handle (*.base.eth handles often are) squeezed the 22 px picture into an 8 px oval, wrapped the handle over three lines and sent the time to the next line, a "·" at its start, and in a reply the collapse toggle sat alone on its line. Now who wrote a cast and when keep to one line, in a feed card, a quoted cast and a reply alike: the picture keeps its size, the name and the handle are cut short with an ellipsis (short of room they share it evenly, so a short one shows whole), and the time keeps its place, the dot before it its own; the channel and the app follow, on the next line if they must. At 390 px a long-named author's card header went from four lines (85 px) to two (46 px), and a quote's from four to one (22 px). A profile card shows a long name and handle a line each, cut short with an ellipsis, where it wrapped them over five lines. A profile's bio breaks a word or a link longer than its line, which ran 33 px past it on a phone, and "fid 99" no longer breaks between "fid" and its number.

  • On a phone, a tap beside an open preview only closes it. With a likes, recasts or replies preview open, a tap on the card around it closed the preview and opened the post under the tap as well (2 of 2 taps in a test, and on allthing.social). Now the first tap outside a panel that a tap opened only closes it, whatever is under it, a card, a link, a name or another count, and the next tap does what it does. A profile card, opened by tapping a name, and one opened from a preview's list, which leaves the list open, follow the same rule; a dialog opened from a preview ("See everyone →") still takes its first tap, and Escape still closes a panel. On a computer, a preview the pointer opened still closes as it leaves, and a click elsewhere still does what it does; React's picker, which a click opens, closes at the first click outside it, which does nothing else.

  • On a phone, every dialog fits the screen, with its × on it. The full list of who liked or recasted a cast ("See everyone →" under a count's preview) was wider than a 390 px phone: on allthing.social it ran from 20 px to 410 px, its × half off the screen, and with long names it was 440 px wide and its × entirely off the screen. It is now 350 px wide, 20 px in from each side. A dialog's width no longer follows what it holds, so signing in, approving Allthing, a hand-off to the Farcaster app and the composer can't run past a phone's edge either: given a line wider than the screen, each stays 350 px wide, where each was pushed off the screen. In the list, and in a count's preview, a name and a handle keep to one line each, cut short with an ellipsis when long; short of room, the two share it evenly, so a short one shows whole. A long handle wrapped onto a second line, at 1280 px too: a row with one is 38 px tall again, where it was 55 to 57 px.

  • The composer no longer jumps when a picker opens: it opens near the top of the window and grows down, so its top and your draft stay put. On a phone it is a sheet from the bottom of the screen, as wide as the screen, with the bar and Post in reach of your thumb. Opening the stickers on a short phone no longer scrolls your draft out of view under the composer's header: the panel scrolls under the draft instead.

  • CI's browser jobs run again on every pull request. GitHub has begun moving ubuntu-latest to Ubuntu 26.04, where Playwright 1.56 can't install Chromium ("Playwright does not support chromium on ubuntu26.04-x64"), so a job that landed on the new image failed before running a test. Every job now runs on ubuntu-24.04, as they all did until today.

  • GIFs no longer take a photo's room. A GIF from Tenor, KLIPY or GIPHY, or any image whose address ends in .gif, shows at about the size Discord gives one: its own shape, at most 300 px wide and 300 px tall, from the left edge of the cast's text, with rounded corners, and on a phone at most 70% of the card's width. It was as wide as the card and up to 70% of the window tall. A Tenor GIF that comes as a video is sized the same, and so is the GIF of a cast you've just handed to the Farcaster app (at most 240 px before); among a cast's other images, a GIF is a tile like theirs, as before. Photos and videos show as before. A GIF in a feed still opens its post, where a click or a tap opens it in the media viewer at its own size, and Back to a feed lays GIFs out at their size at once, as photos. A GIF on Cloudflare Images, where the Farcaster app keeps the images people upload, still shows as a photo: its address has no extension and the data source sends no content type, so nothing says it is a GIF.

  • A cast, a reply or a quote posted from Allthing with your key now shows at once where it belongs, and after a reload too. A cast shows above the home feed and on your own profile's Casts tab (and above its channel's feed); a reply shows under its post and on your Replies tab. Each links to itself, until the page lists it. Before, it showed only in a notice, and your profile and feeds didn't have it until their cached copies aged out, up to five minutes later. After a cast, a reply, a like, a recast or a delete, the Worker now refreshes the pages it changes (POST /api/v1/refresh: your own profile, the cast and its replies or its people, the post you were on, the channel). The browser then reads them again past its cache, and again after 4 and 10 s while they don't have it yet. A like or recast keeps the count it made through a reload, where the feed's copy is older. The tab keeps what you posted, and the counts, for 10 minutes. The refresh asks nothing upstream: 1 to 5 cache reads and writes. A cast posted in the Farcaster app after a hand-off also leaves the page above the list once the list has it.

  • Pepe emoji show in a cast's excerpts, as they do in its text. The Replies preview, a post's "In reply to", search suggestions, the composer's reply and quote, the media viewer's caption and the scheduled list showed :pepe_name: as typed. Nothing in an excerpt is linked, as before; in a one-line excerpt, which clips what leaves its line, an emoji is a little smaller, so it shows whole. An excerpt now counts an emoji as three characters, about the room it takes, not as its shortcode's length, and never cuts a shortcode in two: a run of them with no space near the end used to end in a broken :pepe_…, in quoted casts and the tab's title too. A cut that already falls between words also keeps the last one. The front page's early cards cut an image's alt text the same way (web/paint.js, 0.6 KB more at brotli). The app's module grows by 0.6 KB at brotli 11 (463,184 → 463,735 bytes).

  • In the static copy under /Allthing (what scripts/build-pages.sh builds), opening a cast by clicking its card, searching from the search box, clicking a replies count and choosing a time range no longer end on "Page not found". They went to /Allthing/Allthing/…: the router puts its base before a path it opens from code, and these paths had it already. They now open as a link to them does. Links were never affected, and allthing.social has no base.

  • A post page gives one count of its replies, and its rows of counts and buttons keep to one line. The post's row said "19 replies" where the heading over the replies said "Replies 33", counting the replies to replies too; both now count its direct replies, as its card in the feeds does, and never fewer than are listed. On a phone, Reply and Quote are their icons, as React is, their names kept for screen readers, and under a reply the counts are their icons and numbers, shown as the Farcaster app shows them (1.2k for 1,234), the numbers in full and "likes", "recasts" and "replies" left to screen readers there. At 390 px the post's row went from two lines to one, and a reply's from two or three to one, four levels of replies down (three with a thousand likes), with room to spare for a wider font; at 1280 px a reply's row is one line five levels down, where from the third level it took two. A reply's time is its link, and says so to screen readers ("Open reply"); the "permalink" beside its buttons, which went to the same place, is gone.

  • Favorite and Show less say what they do under them, in Allthing's style, where they said it in the browser's tooltip, which showed late and never on a phone: when the pointer rests on one, when keyboard focus is on it, and for 3 seconds after a press. On a phone, a button just turned off no longer keeps the look of one that's on.

  • A popover is in its place from the moment it opens. It was moved inside the screen a frame after it opened, so until then it hung over a phone's edge and the page was as wide as its far side; a test that measured React's picker under a post just then found it 275 px past the edge. Now it's placed in the same moment the press opens it. It also keeps its 8 px from the screen's edge (it kept 5, measured while its opening animation still shrank it), and on an iPhone turned sideways it can use the whole width, as Safari gives the screen's width upright.

  • No frame with nothing to read when a page opens or a list is drawn anew. The page faded in from transparent and a list's cards rose in from transparent, one after another, so every navigation showed 2 to 8 such frames; a post and cards now rise into place on a spring-like curve, readable from their first frame.

  • Pages keep their layout as they load, and as the reader switches lists. A channel's page has its sidebar's place while it loads, so the feed beside it no longer moves over when the channel comes: at 1280 px it showed as one centred column, then jumped 165 px left (layout shift without the footer's own 0.086 → 0.001 on /c/farcaster). And new shows the apps its casts came from, as hot and top show their trending casts', so switching between hot and new no longer moves the list, 43 px at 1280 px and 55 px at 390 px; its apps filter new as hot's do hot.

  • On the front page's app bar, an app's card no longer has the browser's tooltip over it. The bar said what it was in a native tooltip ("Apps that posted … of today's trending casts, from the app that registered each cast's signing key."), which showed over any card opened from it after a second; it's read out instead. Segments without a card no longer have one either: the legend under the bar says the same.

  • The usage bar under the front page's tabs no longer overstates the apps it knows while it is still reading the rest. Just after a list is built, most of its casts' apps are still being read in the background, and the bar showed the few known as if they were all of them ("Farcaster 100%" with 14 of 92 known). Each app is now a share of all the casts, and those not known yet are a light grey segment at the bar's end, counted in the legend ("78 not known yet"): 14 of 92 is 15%. The legend is one line whatever it lists, scrolling sideways where it doesn't fit, where it wrapped onto a second line on a phone, so the row is as tall whether the apps are being read or all known, and nothing under it moves as they come in, or between hot and new. With no app known and none coming (the static copy), there's no bar, as before.

  • A profile's card can't be shifted sideways any more. The glow behind the name reaches past the card's sides, and the card that clipped it could be scrolled, so anything that scrolled it, such as focus or find in page, moved the picture and text 40 px to the left, under the card's edge.

  • On a phone, a tap on a cast's heart or recast, or anywhere else in a cast, no longer flashes the whole card in the browser's tap colour, which showed over the dialog the tap opened too.

  • On a phone, the links in a cast's meta line are big enough to tap. Who wrote it (the picture, the name and the handle), when, the channel and the app each take a tap 25 or 26 px tall, centred on them, where they were 15 to 22 px (the time 20 px, the channel 16 px and "via" 15 px), on a feed card, a quoted cast, a reply and a post alike. None meets another target or reaches the text under it, so a tap on a cast's first line still opens the cast. A meta line on one line keeps its height; one that wraps has its second line as tall as the first and 6 px under it (4 px before), and a reply's words and a post's buttons sit 2 px lower, clear of the taps above them. With a mouse, nothing changes.

  • The footer no longer shows while a page waits for its data, only to be pushed down when it comes: a page is at least a screen tall under the header.

  • A channel page shows its newest casts where it showed only its pinned cast, as /veg did. For some channels, Hypersnap sends the newest casts after a page or two with none on them, each with a cursor for more (/veg's after two, every time), and the page took the first, empty one for the whole channel. It now follows the cursor past up to three such pages. If it still finds none, the page keeps the last copy it had, or, with none, says the data source didn't answer and offers to try again, instead of showing the channel as empty.

  • In the emoji picker, the groups bar marks the group in view after a scroll that comes right after choosing a group: it could stay on the group chosen. A jump now remembers where it left the grid, rather than skipping the next scroll, which the browser merges with a scroll by hand in the same frame.

  • Casts about Gig Bot written as two words ("🚀 New Task on Gig Bot 2.0!", "Check out Gig Bot 2.0") are left out as spam, as those writing gig-bot or gigbot already were. One was in the home feed, and six turned up in search.

  • No native tooltip on a favorite's star in feeds: it is named for screen readers, as the rest of the page is, with nothing to cover the cards' own popovers.

  • In Firefox, a video whose playlist claims a little more time than its media holds now loops, instead of stalling on its last frame. There Allthing's own player streams the video (Chrome and Safari play HLS themselves), and it waited for media that would never come, so it never ended the stream. It now ends it once the last segment is in. A playlist that drifts from its media also no longer has the player fetch its last two segments in turn without end (3,529 requests in 12 seconds in the test, now one per segment): buffering goes on segment by segment from the last one appended. Farcaster's own videos weren't affected: the 11 sampled today end at most 0.11 s after their playlists say, and they loop in Chrome 155 both ways, played by Chrome and by Allthing's player.

  • The media viewer's caption is who posted the cast and its words, "@alice · Two photos from the walk", cut to two lines, beside "1 of 2" when there are several. It showed the image's generated alt text, "Image 1 of 2 posted by @alice: …", beside "1 of 2", so it said the image's place twice; the alt text stays on the image, for screen readers.

  • On a phone, the account menu's rows are a fingertip tall (44 px; they were 35), and the header's picture takes a tap a little way around it, a 44 px target, without flashing the browser's tap colour.

  • The GIF picker shows each GIF whole, at its own shape, in rows that share a height, where it cropped them all to squares (a tall GIF's caption was cut off). On a phone it no longer focuses its search box as it opens, so the keyboard doesn't cover the GIFs; Enter searches at once, and on a phone puts the keyboard away. Its tiles no longer show the browser's tooltip.

  • Wallet activity missing a chain is never shown for long. A copy whose read left an EVM chain out (its shares then cover the rest) was refreshed within minutes, but could still be served from the background for an hour after it was read. Before that rule, for a day: on 20 October 2026 the first reader of 0xael.eth's profile saw Optimism 58% and Monad 41%, with Base, 98% of its transactions, unread. After ten minutes, a reader now waits for a new read. 0xael.eth's counts were cross-checked against each chain's RPC, and all nine match (docs/WALLETS.md).

  • No native tooltips on a post or in a popover. The browser's own tooltip showed in the system's style over Allthing's solid popovers: on a reply's time in the Replies preview, and on a profile card's join date, vote, wallet bar and legend. It also showed on a post's times, its app and "OP". What the tooltips said is read out by screen readers instead, and a post's signing key now shows in its meta line ("via Farcaster · key 0x42ab…2bf4").

  • The past-month chart on /dau labels the days under their own bars. While reading caught up, its one label named today, whose slot was still empty, and sat under the day being read: 19 October's bars said "20 Oct". Its labels, a week apart, now count back from the newest day with a bar.

  • A page that can't load says what happened in a reader's words, and what they can do, where it showed the API's own message ("Network error: could not reach Allthing's API", "Farcaster data source is busy; backing off (try again in 5s)"). A link to a cast, an account or a channel that doesn't exist says so ("No such cast: it doesn't exist, or it was deleted."), and a search too long says "That search is too long", each without a "Try again" that couldn't help, as no 4xx answer has. A busy data source is tried again on its own when the Worker says it may be (usually a few seconds; the panel says when), three times in a row at most, with "Try again now" meanwhile; offline, or with the server failing, "Try again" stays. Each sets the page's title ("Cast not found · Allthing", "Busy · Allthing"), which stayed "Allthing".

  • A profile card that opens before its vote and wallets arrive (after the half second it waits for them, from a Liked by, Recasted by or Replies preview, say) no longer grows when they do: it grew by 27 px for an account with two chains, and by 115 px for one with five chains and a note. The vote row is always there ("Unknown" when the vote can't be read), and the wallet section keeps one height whatever comes: the bar with one line of legend (two chains by name and "+N" for the rest), or one message, with any note folded into a word beside the "Wallet activity" label ("partial" or "earlier counts") that explains itself on hover and to screen readers.

  • A profile's header keeps its height as its vote and wallet activity come, each from an endpoint of its own after the profile. It grew as they did and pushed the tabs and casts down: 80 px at 1280 px and 100 px at 390 px on dwr's profile, about 25 px on one with no verified wallets. Now the vote's row is there from the start, its pill as tall as its placeholder, and the wallet section is one block whatever it says: its bar and one line of legend, four chains by name where there's room (two or one on a phone) and "+N" with their share together, so what shows adds up to 100 and nothing is cut off, with any note behind a word at the line's end ("partial", "earlier counts") that shows it when pointed at, focused or tapped. An account with no verified wallets, which the profile itself says, shows the line that says so at once, keeps no room for a chart, and isn't asked about, by the profile or by its casts' cards: a profile view there makes 2 requests, not 3. Layout shift without the footer's own, on allthing.social before and locally after: dwr's profile 0.015 → 0 at 1280 px and 0.033 → 0 at 390 px.

  • The sign-in dialog no longer says Allthing can't post or change anything for the reader, which stopped being the whole story once it could like, recast and post. It says that signing in tells Allthing who they are, nothing more, and that liking, recasting and posting take a key they approve once in the Farcaster app.

  • The production performance check also accepts a deployment of a later commit that includes the pushed one. Workers Builds skips a commit that a newer push supersedes, so after a docs-only merge (which runs no CI) the pushed commit itself could never go live, and the check would wait out its 30 minutes and fail.

  • The production performance check waits up to 30 minutes for the pushed commit to go live, not five. Workers Builds takes 6 to 16 minutes, and about 25 when two merges queue, so since the wait was added the check failed on every push to main without measuring anything.

  • A profile card opened from a Liked by or Recasted by list shows the person's bio, follower and following counts and join date, like cards elsewhere, instead of "The bio and follower counts are on the profile". The lists carry them, from the user objects that come with the reactions: no extra request, and the preloaded lists bring them too.

  • Nothing a reader does fails where it needn't, so the browser's console stays quiet. Signed out, pointing at Cast, Reply or Quote no longer asks for the GIF key, which the Worker gives only to a reader signed in (a 401). A batch of previews the Worker couldn't read comes as an empty batch, kept nowhere, instead of a 503, and each preview asks for itself as it opens, as before. A post's like and recast are settled by the reader's latest hundred of each when they reach back to the cast, a reaction coming after its cast, so only an older cast is asked about (the node answers a reaction that isn't there with a 400). The last native tooltips are gone, on the sort tabs, the footer's version, the composer's clock, wallet addresses and channel counts, what they said read out instead where the page doesn't say it; and so is the help cursor over a card's "via" and a wallet note, which had nothing to show.

  • In the Replies preview, each reply's author is a link to their profile, and hovering, focusing or tapping it opens their profile card beside the preview, as names in Liked by do, with the bio, counts and join date that come with the replies. The reply's own text still opens the reply.

  • A reply with no text no longer shows as a blank gap in the Replies preview: its image thumbnails load at once (they were left to lazy loading, which left them blank there), and a video, link or quoted cast is named in a few words.

  • A profile card opened from a name in a Liked by, Recasted by or Replies list sits level with the list, the way the list opened, instead of hanging from the name: lower names put it past the list and the post it belongs to. On a phone, with no room beside the list, it still opens by the name. Its wallet notes have room between them.

  • A profile card's picture no longer flashes blank as the card opens: until its sharper copy has loaded, it shows the small copy the feed already loaded. A post's author and a profile's own picture do the same, and load at once instead of lazily.

  • The boot screen's header is the app's: Home, the search box and Sign in (or your picture, or the static copy's read-only note) are there from the first paint, instead of popping in when the app comes.

  • Wallet activity no longer swings when a chain can't be read: that chain keeps its last good count from the previous read of the same addresses, and the chart says some counts are from an earlier read. Before, it was left out and the other chains shared its part (0xael.eth showed Optimism 58% and Monad 41% while Base, its main chain, was unread), and that copy was kept for six hours. A copy still missing an EVM chain is now read again within minutes.

  • On a phone, a tap on the lower edge of a photo, a link card or a quote just above a card's likes and recasts is the photo's, the card's or the quote's, where it could like or recast the cast instead: the row's tap bands, 44 px tall, reached 3 px into what was above them (4 px under a reply). On a touch screen the row sits 14 px under a card's content and 13 px under a reply's (9 and 7 px with a mouse), its bands 2 px clear of it.

  • Wallets distinguish unread chains from measured zero activity, and keep the earlier-read notice when carried counts are zero.

  • Search and the channel directory's search no longer show a channel count that contradicts the channel's page: Hypersnap's channel search can return a different record of the same channel, so /farcaster showed "23 casters" in search while its page shows 445,633 followers. Search rows now show the channel without a count.

  • A post's meta line names the app it was posted from once ("via Farcaster"), not twice: the feed card's "via" chip had stayed next to the post's own linked one since the app moved into the meta line.

  • Re-sorting a post's replies redraws only the replies, where it drew the whole post again (192 to 224 ms on a slow phone), and the sorts take each other's place in the history, so Back leaves the post instead of stepping back through them.

  • Taps, clicks and keystrokes show their result sooner on every page: about 16–32 ms instead of 40–64 ms after the input in a headless browser without a GPU. The line of light drifting along the header animated its background, which repainted the header in every frame while it ran (always), and the next frame after an input waited behind that work. It now slides by transform, which the compositor animates without repainting; it looks the same and still stops under reduced motion.

  • Typing in the composer no longer waits behind the header: while a dialog is open, the header's drifting line and the wordmark's sheen stand still behind its blurred backdrop. Moving, they had the browser blur the whole screen again in every frame, and in a browser without a GPU each key typed waited 128 to 136 ms to show on a computer; now 16.

  • Back puts the card you were at back within a pixel of where it was laid out. With a mouse, the card's 1 px lift under the pointer was kept as its place, and the restore let up to 2 px go, so it could come back 3 px off; cards drawn as Back restores your place no longer rise into it again.

  • Wallet activity by chain follows the current rules even when the edge serves a payload it cached under older ones, which it keeps for hours. For up to a day after 0.4.0, a chain under 1% could still show as 1% (0xael.eth's Optimism, 0.3% of its transactions). The browser now works out the shares from the counts itself.

  • Wallet activity says when Solana isn't counted. Solana's public RPC refuses Allthing's requests (from browsers, and so far from Cloudflare Workers), so an account's Solana wallets were left out of the chart without a word; an account whose only wallets are on Solana read "No onchain activity". The chart now says "Solana isn't counted: its public RPC refuses Allthing's requests", and the wallet payload's sources no longer claims Solana was read.

  • Older cached wallet responses also redact private Solana RPC diagnostics without losing counts, carried flags or cache policy.

  • At 1280 px, search results are in the feeds' column: 770 px wide and centred, as the front page's cards are, where they ran 1,100 px across the page. People come two to a row and channels three; a phone's column is as it was.

Removed

  • The Farcaster Direct Casts flow in Messages: the Farcaster API key (wc_secret_…) setup, the key kept in IndexedDB (allthing-dc) and the menu's Forget Direct Cast key, the browser's calls to Farcaster's Direct Cast API, allthing_core::dc, and the Worker's Direct Cast relay (/api/v1/dc/…, DC_RELAY, DC_ORIGIN, dc_relay in /api/v1/status), with their tests and docs/DIRECT_CASTS.md. Farcaster's Direct Casts stay in the Farcaster app. The Messages part is smaller (see docs/PERFORMANCE.md).

  • The temporary per-stage marks in API answers' Server-Timing header, added to measure uncached profile builds, are gone now that the measuring is done. The header still carries the Worker's total time and how the answer was served.

  • Profiles and profile cards no longer show the Farcaster app's spam label for an account ("Labeled likely spam", "Not labeled spam", "Nerfed", "no label") or the note that came with it. The label shards the build compacted from that app's public dataset are gone too, with the cargo xtask labels task, and builds no longer download the 125 MB dataset. Leaving out spam by a cast's content is unchanged.