Skip to content
This repository was archived by the owner on Oct 28, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Allthing is a Farcaster client for reading, written in Rust: a Leptos WebAssembly app plus a workers-rs API Worker, deployed to Cloudflare Workers (Free plan) at https://allthing.social. Read `docs/ARCHITECTURE.md` first.

- **One QR flow: sign in, and write, with a key the reader approves once.** By the owner's decision, where the Worker endorses keys there is one approval and one dialog: the reader approves Allthing's key request in the Farcaster app (a QR code on a computer, a link to the app on a phone), which signs them in (`POST /api/v1/auth/key`) and lets them cast, reply, quote, like, recast, follow and delete from here, with no second scan or separate grant step. A reader signed in without a key in this browser (signed in before keys, after Revoke permissions, or with its storage cleared) meets the same dialog, in its approve form, on their first write (`Session::grant`), never by itself on a page load. Never add a second approval dialog or QR flow. Sign In With Farcaster by QR code is only the fallback where the Worker endorses no keys, and makes no relay request where it does (`allthing-worker/src/auth.rs`, `allthing-core/src/siwf.rs`, `allthing-web/src/components/signin.rs`; see `docs/ARCHITECTURE.md#sign-in`). Once a reader has approved Allthing in the Farcaster app, casts, replies, quotes, likes, recasts, follows and deletes go from Allthing: the browser makes and keeps the key and signs (`allthing-core/src/app_key.rs` and `message.rs`, `allthing-web/src/keys.rs` and `components/writer.rs`; see `docs/ARCHITECTURE.md#liking-and-recasting`), and the Worker keeps scheduled casts the browser signed until their time (`allthing-worker/src/scheduled_casts.rs`, `allthing-data/src/scheduled.rs`). Allthing asks for those keys as its own Farcaster account, @allthingapp (FID 3356318, `ALLTHING_FID` in `wrangler.toml`), which its owner registered for it, and shows the casts "via Allthing" (`allthing-core/src/clients.rs`). By the owner's decision that is Allthing's only account: never add another, a seed phrase import or a wallet connection. The Worker endorses readers' keys with that account's custody key, the `ALLTHING_CUSTODY_KEY` secret, set in Cloudflare only (`allthing-worker/src/app_key.rs`; see `docs/DEPLOY.md#secrets`), and uses it for nothing else; it never holds a reader's key or signs as a reader. Until the secret is set (`writes.ready` in `/api/v1/status`), and for a reader signed out, a write opens the cast, the profile or a draft ready to post in the Farcaster app instead (`allthing-web/src/components/handoff.rs`, `allthing-core/src/compose.rs`; see `docs/ARCHITECTURE.md#handing-writes-to-the-farcaster-app`). Never add other login methods, wallet connections or write paths unless asked to. Messages (`/messages`) is Allthing's own, on SpacetimeDB (`allthing-web/src/pages/messages`, `spacetime/`; see `docs/ARCHITECTURE.md#messages-on-spacetimedb`): never add Farcaster Direct Cast API calls or an API-key paste step to it unless asked.
- **Never hide content** because of who posted it. Allthing shows no account labels; spam is judged by a cast's content (see Mini apps and spam below).
- **No automatic account-based exclusions.** Allthing shows no account labels; spam is judged by a cast's content (see Mini apps and spam below). The owner's explicit exception is reader-selected private Mute and Block: hide content involving those accounts throughout that reader's Allthing view, including identifiable mentions, quoted/replied-to/recasting accounts and notifications. Keep these preferences caller-private and synced through SpacetimeDB; Block also rejects new Allthing DMs in either direction using verified FIDs. This does not delete public posts or establish a block in another Farcaster client. Preserve the recognition limits and undo/sync explanations in `docs/ARCHITECTURE.md#private-mute-and-block`; never turn these choices into a shared author blacklist.
- **Rust first.** The owner prefers Rust for application behavior and native tooling. Put pure shared rules in `allthing-core`, interactive UI in `allthing-web`, and native build/check policy in `xtask`. Keep `web/paint.js`'s inert first-card renderer: the owner chose it after a paired cold-load comparison beat the Rust prototype (see `docs/PERFORMANCE.md#measured-exception-the-javascript-first-card-renderer`). Retain the existing Node static test server (`e2e/pages-server.mjs`) after an unresolved pre-mount stall in migration CI; the server's role in that stall was not established (see `docs/PERFORMANCE.md#retained-node-static-test-server`). Keep the small JavaScript loader and inline preload/data hints, the SpacetimeDB TypeScript SDK adapter/generated bindings, and official Playwright browser tests at their platform boundaries. CSS, SQL, HTML and config remain platform data. Preserve measured performance, license rules and independent test oracles; do not mask handwritten files with Linguist attributes.
- **Where code goes.**
- Pure logic and data types, with unit tests, go in `crates/allthing-core`.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ All notable changes to Allthing are recorded here. The format follows [Keep a Ch

### Added

- Private, account-wide Mute and Block controls on profiles and hover cards, with searchable undo controls in Settings and keyboard/touch-accessible help. Preferences sync through SpacetimeDB and suppress posts, quoted/replied-to/recasting accounts, identifiable mentions and account links, notifications and their unread counts. Block also hides existing Allthing conversations locally and rejects new DMs in either direction using verified account ownership; public posts and other Farcaster clients are unaffected.
- Durable offline mute/block intent, independent per-choice revisions and retained clear tombstones. Fresh signed-in sessions wait for private filters before drawing content; realtime changes remove already shown content, and newer remote choices supersede stale offline intent.
- A private Library for bookmarks and seen history synced across signed-in devices through SpacetimeDB, plus paginated Farcaster likes and recasts. Seen history no longer expires after 30 days or 2,000 posts; fresh Home candidates wait for synced history, while already drawn cards keep their place. Library includes loaded-post filtering, activity times, retry, Back restoration, Mark unread and Clear history controls.
- Sync notification read progress across signed-in sessions with a private, attested SpacetimeDB watermark. The existing local cache works offline; a delayed background connection uses separate credentials without creating chat presence or DM keys.
- Videos up to 250 MB upload in 8 MB chunks. Retry continues from the last acknowledged chunk; Remove cancels the pending upload. Preparation and finishing have visible states, and the cast stays disabled until its attachments are ready.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Allthing is an independent Farcaster client, written in Rust, that shows trendin
- **Provenance.** Each cast names the app that registered its signing key ([docs/PROVENANCE.md](docs/PROVENANCE.md)).
- **Daily active accounts.** [/dau](https://allthing.social/dau) counts Farcaster's active accounts from the network's event stream, exactly as [SnapMeter](https://github.com/ael-dev3/SnapMeter) does.
- **Allthing's poll.** One question on the front page: would you like an Allthing mobile client?
- **Less noise, judged by content.** Spam caught by a few fixed content rules is left out by the Worker. Casts that embed or link to mini apps or frames, which Allthing doesn't run, are hidden too unless you turn on Show mini app casts in Settings; then each links to open in Farcaster. Nothing is hidden because of who posted it.
- **Less noise, judged by content.** Spam caught by a few fixed content rules is left out by the Worker. Casts that embed or link to mini apps or frames, which Allthing doesn't run, are hidden too unless you turn on Show mini app casts in Settings; then each links to open in Farcaster. There are no automatic account-based exclusions; signed-in readers can choose their own private Mute and Block preferences.

### Signed in

Expand All @@ -52,13 +52,14 @@ Allthing is an independent Farcaster client, written in Rust, that shows trendin
- **A composer like the Farcaster app's.** Images and videos (PNG, JPEG, GIF, WebP; MP4, MOV, WebM) by the attach button, paste or drop, with photo location data stripped in the browser; @mention autocomplete; a Pepe emoji picker, animated Pepe stickers and GIFs (from [KLIPY](https://klipy.com)); drafts that last; a sheet on the keyboard on a phone.
- **Library.** Bookmarks, Likes, Recasts and Seen, with paginated history, activity times, a filter for loaded posts and Back restoration. Save posts privately without a Farcaster signing key; mark posts unread or clear reading history across devices while keeping bookmarks.
- **Notifications.** A bell with what's new, and /notifications with likes, recasts, replies, mentions, quotes and follows, grouped as the Farcaster app groups them.
- **Private Mute and Block.** Use the buttons on another account's profile or hover card to hide posts and notifications involving it, including identifiable mentions, quotes, replies, recasts and account links. Block also stops new Allthing DMs in both directions. Choices sync across signed-in sessions through SpacetimeDB; Settings → Muted and blocked accounts lets you find and undo them separately. The help button explains pending sync, recognition limits and the scope: public posts remain public, other Farcaster clients are unaffected, and arbitrary names or text in images cannot always be recognized.
- **Messages.** One inbox on SpacetimeDB at /messages: Allthing's public `#general` room and end-to-end encrypted conversations ([docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#messages-on-spacetimedb)).
- **Signed out**, a write opens the cast, the profile or a ready draft in the Farcaster app instead.

### Fast and frugal

- **One API call per page**, cached at the edge, so the whole site runs on the Cloudflare Workers Free plan.
- **Early paint.** For anonymous readers, a small JavaScript renderer draws the front page's first cards from its existing response while the Rust app downloads. Signed-in Home waits for private history membership to avoid flashing previously read posts. It stays in JavaScript because it beat the Rust prototype in the [paired cold-load comparison](docs/PERFORMANCE.md#measured-exception-the-javascript-first-card-renderer).
- **Early paint.** For anonymous readers, a small JavaScript renderer draws the front page's first cards from its existing response while the Rust app downloads. Signed-in Home waits for private history membership and account filters to avoid flashing previously read or muted/blocked posts. It stays in JavaScript because it beat the Rust prototype in the [paired cold-load comparison](docs/PERFORMANCE.md#measured-exception-the-javascript-first-card-renderer).
- **Split WebAssembly.** Pages, the composer, writing, sign-in and the DAU dashboard are code of their own, fetched when used, so every visit downloads less.
- **Instant navigation.** Posts open from the feed's own data, cards are prefetched within a capped budget, and Back returns to the same place. CI holds smoothness to budgets ([docs/PERFORMANCE.md](docs/PERFORMANCE.md)).
- **Accessible.** Keyboard throughout, alt text, focus kept in dialogs, reduced motion respected, and it fits a phone.
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
9 changes: 8 additions & 1 deletion crates/allthing-core/src/drafts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ mod tests {
fid: 42,
username: "alice".into(),
excerpt: "What next?".into(),
..SavedCast::default()
}),
..SavedDraft::default()
};
Expand All @@ -139,7 +140,13 @@ mod tests {
#[test]
fn each_draft_is_kept_for_what_it_is_for() {
let quote = SavedDraft {
quote: Some(SavedCast { hash: "0xAB".into(), fid: 7, username: "bob".into(), excerpt: String::new() }),
quote: Some(SavedCast {
hash: "0xAB".into(),
fid: 7,
username: "bob".into(),
excerpt: String::new(),
..SavedCast::default()
}),
..SavedDraft::default()
};
let books = SavedDraft { channel: Some("books".into()), ..SavedDraft::default() };
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/emoji.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/following.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/latest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ mod tests {
recasts: 0,
replies: 0,
mini_app: false,
references: Default::default(),
spam: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/allthing-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod media;
pub mod message;
pub mod metadata;
pub mod model;
pub mod moderation;
pub mod normalize;
pub mod notification_state;
pub mod notifications;
Expand Down
5 changes: 5 additions & 0 deletions crates/allthing-core/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ pub struct Cast {
pub text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mentions: Vec<Mention>,
/// Account provenance that an excerpt cannot retain: known reply parents,
/// recasters and inline quoted-cast identities. Reader filtering also
/// checks this cast's own author, mentions, text and hydrated quotes.
#[serde(default, skip_serializing_if = "crate::moderation::References::is_empty")]
pub references: crate::moderation::References,
/// Unix seconds.
pub ts: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down
Loading
Loading