Skip to content
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
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to Allthing are recorded here. The format follows [Keep a Ch

### Fixed

- A hover card dismissed with Escape cannot reopen when the pointer re-enters during its closing animation.
- A data center with no default 24-hour Home copy, or an expired retained one, can use a fresh public copy shared by another data center, preserving its original age and current time bounds, instead of waiting for the full upstream build. Missing or invalid copies retain the normal fallback, ordinary background refreshes still build normally, and other ranges and private feeds are unchanged.
- CI rejects browser tests that pass only on retry, so a flaky run cannot appear as a clean release check.
- Choosing an emoji group while the picker is still drawing reaches its heading on the first click, including a group at the temporary bottom of the list.
Expand All @@ -27,17 +28,25 @@ All notable changes to Allthing are recorded here. The format follows [Keep a Ch
- Following prefetches its own feed when changing sort or range, stops automatic paging at the time limit, and resumes from that cursor for a wider range. A fully seen or filtered feed offers Load more posts after ten payloads rather than reading indefinitely.
- Changing accounts replaces the feed and ranking snapshot, and late answers from the previous account cannot refill its cache. Presence no longer counts an anonymous and signed-in visit twice; hiding or signing out also removes the public name from cached lists.
- Messages connects under the existing Content Security Policy without permitting JavaScript evaluation. Pressed conversation rows keep their previews and timestamps readable.
- Notifications coalesce refreshes from opening the page and regaining focus. Marking the list read no longer queues a duplicate request that could start after the tab is hidden.
- Notifications share the bell's initial read even when it finishes before the page's code arrives. A failure remains visible for Try again, and a fresh answer is reused. Opening the page, regaining focus and marking the list read no longer queue duplicate reads behind an in-flight refresh or after the tab is hidden.
- A wrapped reply's app link clears the collapse button's full tap area.

### Development

- Pin the current Miniflare development dependency's Undici to 7.30.1, fixing its [EventSource memory](https://github.com/advisories/GHSA-4qv8-2p9j-2685), [integrity-check](https://github.com/advisories/GHSA-f72v-6w45-q472) and [content-encoding](https://github.com/advisories/GHSA-xjj6-f33q-929c) advisories. The version-scoped override leaves Wrangler, Miniflare and workerd unchanged; Wrangler 4.152.0 also still pins an affected version.

- Smoothness checks retain bounded, sanitized raw input, long-task, animation-frame and frame diagnostics after each profile closes, including on failure. Metric inputs, strict checks and timing limits are unchanged.

- Live Worker checks pace API traffic between tests, within the existing per-IP limit, and retain bounded request counts and sanitized failed-response diagnostics. Interaction timings, assertions and retry counts are unchanged.

- Back-navigation checks wait for sampled Home and post frames before returning, preserving their exact retained-card, restored-height, position and request assertions.

- CI runs functional browser fixtures with two workers, followed by smoothness measurements alone with one. Both phases run and keep separate reports even after a failure, under the same checks and timing limits.
- Video keyboard checks verify a held Space press on the original pointer-focused control before release, retaining bounded input and media diagnostics on failure.
- Cold-load performance checks retain sanitized per-sample LCP, network, cache and long-task diagnostics, including on failure, in a seven-day Actions artifact. Their timing limits, cold contexts and sample count are unchanged.
- Browser regressions use past timestamps relative to the page’s own clock, wait for restored seen rows and the latest presence-cache expiry, and preserve their ordering, privacy, navigation and timing checks, including a deliberately delayed count refresh.
- Browser checks recover read posts through Show seen posts, distinguish media uploads from analytics XHRs, control typeahead's input intervals and settle initial fixture resources before measuring reply-sheet shifts. Live checks observe pagination retries, available wallet data and the deployed inline-fetch/CSP contract without changing their timing limits.
- The roadmap lists the participating and Messages features already delivered, and the architecture's static-test command matches CI.
- Startup checks distinguish the main WebAssembly module from idle-loaded parts, and range navigation waits for the actual New feed response.
- Local macOS builds find worker-build's cached WebAssembly optimizer. SDK wire-format and strict-CSP browser checks cover Messages; feed-ranking, reply-composer and smoothness fixtures exercise the updated UI without changing their budgets.
- The attestation-key helper replaces the complete Rust key array, including multiline values, so a generated key compiles.
Expand Down
17 changes: 15 additions & 2 deletions crates/allthing-web/src/components/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ impl Notifications {
self.whose() == Some(fid)
}

/// The first page's times, as last read (tracked).
/// The first page's times once its latest refresh succeeded (tracked).
/// Old times must not start a page read behind a refresh that can fail.
pub fn times(self) -> Option<Arc<Times>> {
self.0.with(|state| state.times.clone())
self.0.with(|state| if state.loading || state.failed.is_some() { None } else { state.times.clone() })
}

/// Why the last read failed, if it did (tracked).
Expand All @@ -135,6 +136,18 @@ impl Notifications {
self.0.with_untracked(|state| (state.whose == Some(fid)).then_some(state.seen)).unwrap_or_else(|| seen_of(fid))
}

/// Opening the page shares the bell's initial read, including a failure
/// that arrived before the page's code. Only Try again or a later refresh
/// should retry that failure; a fresh answer is already ready to reuse.
pub fn opened(self) {
if self.0.with_untracked(|state| state.loading || state.failed.is_some())
|| api::peek::<Times>(NOTIFICATIONS_PATH).is_some()
{
return;
}
self.refresh();
}

/// Read the first page again for the reader signed in, unless a read
/// within a minute can be reused or is on its way (`api::get`).
pub fn refresh(self) {
Expand Down
11 changes: 7 additions & 4 deletions crates/allthing-web/src/components/popover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,13 @@ pub fn Popover(
let enter = move |_: ev::MouseEvent| {
warm();
if hovering() {
if state.get_untracked() == State::Open {
cancel();
} else {
schedule(open_delay, Box::new(open));
match state.get_untracked() {
State::Open => cancel(),
State::Closed => schedule(open_delay, Box::new(open)),
// Escape has dismissed this panel. A pointer entering while
// it fades out must not arm another open: a profile's dwell
// is shorter than its closing animation.
State::Closing => {}
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions crates/allthing-web/src/pages/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub mod with_worker {
let more = RwSignal::new(Vec::<Arc<NotificationsPayload>>::new());
let read_failed = RwSignal::new(None::<api::Failure>);
// Marking the page seen changes the bell's state too, but not its
// times. Only a different set of times should ask for the page:
// times. Only a settled refresh should ask for the page:
// an async read queued by mark_seen could otherwise run later,
// after its cached answer expires or while the tab is hidden.
let times = Memo::new(move |_| notifications.times());
Expand All @@ -251,7 +251,7 @@ pub mod with_worker {
notifications.mark_seen(fid, page.newest);
}
});
notifications.refresh();
notifications.opened();
let pages = Memo::new(move |_| first.get().into_iter().chain(more.get()).collect::<Vec<_>>());
// Whether the first page is in: a newer copy of it updates the rows
// in place rather than drawing the list anew.
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ The header's small "● 12" says how many people are reading Allthing now. Hover
- **Lints**: `scripts/check.sh` runs rustfmt and Clippy with `-D warnings` for the native and wasm32 targets, and the `cargo deny` license audit.
- **Tooling**: `npm run test:tools` checks the static test server with temporary fixture directories (malformed paths, root boundaries, redirects and deep-link fallback), and the production deployment wait against a local HTTP server (revision matching, transient failures and bounded timeouts). CI runs it before the browser fixtures.
- **Browser regressions**: `npx playwright test '\.fixture\.spec\.ts$'` runs deterministic browser tests against the local build, with API and media responses supplied by fixtures. CI runs these on pull requests and pushes to `main`; new deterministic suites should use the `.fixture.spec.ts` suffix. They exercise the code under review without depending on live data. Failure reports are uploaded for 7 days.
- **Static payloads**: CI also builds the `/Allthing` copy and runs `E2E_PAGES=1 npx playwright test e2e/static-body-limit.fixture.spec.ts e2e/static-metadata.fixture.spec.ts e2e/static-navigation.fixture.spec.ts e2e/preloaded-replies.fixture.spec.ts e2e/poll.fixture.spec.ts`. These fixtures exercise the browser Host's capped HTML streams, cleanup after read failures, complete uncapped JSON responses, and linked-page metadata filtering with inert fallback text and actual frame declarations, check that what the app opens itself (a cast's card, a search, a replies count, the time range) stays under `/Allthing`, that a replies preview built in the browser, by the code a popover batch builds replies with, leaves out spam and mini apps, and that the copy has no poll.
- **Static payloads**: CI also builds the `/Allthing` copy and runs `E2E_PAGES=1 npx playwright test e2e/static-body-limit.fixture.spec.ts e2e/static-metadata.fixture.spec.ts e2e/static-navigation.fixture.spec.ts e2e/static-follows.fixture.spec.ts e2e/preloaded-replies.fixture.spec.ts e2e/poll.fixture.spec.ts`. These fixtures exercise the browser Host's capped HTML streams, cleanup after read failures, complete uncapped JSON responses, and linked-page metadata filtering with inert fallback text and actual frame declarations, check that what the app opens itself (a cast's card, a search, a replies count, the time range) stays under `/Allthing`, that follows navigation works under that prefix, that a replies preview built in the browser, by the code a popover batch builds replies with, leaves out spam and mini apps, and that the copy has no poll.
- **Performance**: `e2e/perf.spec.ts` checks the budget in [PERFORMANCE.md](PERFORMANCE.md); CI runs it against production after each push to `main`.
- **Writes land, or are taken back**: `e2e/verified-writes.fixture.spec.ts`, at 1280 and 390 px, likes, recasts, quotes and their undoing with the reader's real WebCrypto key against a stand-in node (`e2e/helpers/node.ts`, which every spec that writes uses). The stand-in checks each message as Snapchain would (BLAKE3 of its data is its hash, its Ed25519 signature is by the key the reader approved, mainnet, the reader's fid, the time) and answers the read-backs from what it merged. It covers a write merged and shown; taken but never shown, which is undone after 15 s, with Retry; refused, with the node's reason; an unknown key, which offers the approval; a busy node, then success; a lost answer, the same message again and a duplicate, counted once; each undoing; and a quote shown at once, posted once shown, or gone with its draft back.
- **A reply in its thread**: `e2e/reply-in-thread.fixture.spec.ts`, at 1280 and 390 px: a reply at the top of its post's replies the moment Reply is pressed, before the node answers, as posting (faded, its actions inert), then posted, with its time linking to it, and once when the page lists it; a reply to a reply under it; the count on the post and in the feed; refused, or not shown in 15 s, gone with the count back and the composer open again with the draft and why, and Retry sending the same message.
Expand Down
18 changes: 13 additions & 5 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
# Roadmap

Milestones, not dates. Releases use SemVer ([RELEASING.md](RELEASING.md)); details per release are in the [changelog](../CHANGELOG.md). Allthing reads; the owner has since scoped a second phase, one feature at a time: Sign In With Farcaster by QR code first, then a post and reply composer.
Milestones, not dates. Releases use SemVer ([RELEASING.md](RELEASING.md)); details per release are in the [changelog](../CHANGELOG.md). Allthing supports reading, participating through a reader-approved Farcaster key, and its own Messages.

## Done

Expand All @@ -27,20 +27,28 @@ Milestones, not dates. Releases use SemVer ([RELEASING.md](RELEASING.md)); detai
- [x] mini app filtering;
- [x] a simpler interface;
- [x] a release process.
- **Since 0.3.0, unreleased** ([changelog](../CHANGELOG.md#unreleased)):
- **Reading improvements since 0.3.0** ([changelog](../CHANGELOG.md)):
- [x] a Reddit-style time filter on the front page;
- [x] each cast's whole text in feeds, and media at its own shape;
- [x] video sound that stays on once the reader turns it on;
- [x] spam, and mini apps on their own domains, left out;
- [x] a plainer interface, without Neynar scores, the Channels sidebar or "Updated N min ago";
- [x] browser regression tests on every pull request.
- **Participating and Messages**:
- [x] one Farcaster approval to sign in and write, with the key kept in the browser;
- [x] casts, replies, quotes, likes, recasts, follows and deletes;
- [x] a composer with attachments, GIFs, stickers, emoji and scheduled casts;
- [x] notifications and verified Farcaster accounts in Messages, with end-to-end encrypted DMs.
- **0.5.1 release candidate** ([changelog](../CHANGELOG.md#051---2026-10-23)):
- [x] For you and Following tabs, each with hot, new, top and every time range;
- [x] browser-local seen-post history with Show seen posts and reset controls;
- [x] mini app controls in Settings and a responsive Home/online-count header;
- [x] fresh short-range casts and a shared public Home snapshot for cold data centers.

## Next

- **Phase 2: participating.** Sign In With Farcaster by QR code through the Farcaster app ([ARCHITECTURE.md](ARCHITECTURE.md#sign-in)); next, a post and reply composer with a GIF picker (search and trending, from a free GIF API). Posting needs a signer the account approves in the Farcaster app, and that scope comes with the composer.

- **A second backend.** Read the same casts from a Snapchain node (or a second Hypersnap operator). Show which backends returned a cast, and let readers pick the source. Reconcile by message hash, not by text.
- **Smaller app.** The WebAssembly module is about 410 KB compressed, mostly the Leptos runtime and `serde_json`. Candidates: a leaner JSON decoder for payloads, splitting rarely used views, and trimming web-sys features.
- **Smaller app.** Rarely used views already load as separate WebAssembly parts. Further candidates include a leaner JSON decoder, more selective splitting and trimming web-sys features; measurements belong in [PERFORMANCE.md](PERFORMANCE.md).
- **Solana activity.** Find a keyless source of Solana address history that allows browser or Worker requests, or document a key-based option that keeps the key out of the repository.
- **Reading controls kept in the browser.** Local mutes and filters, saved posts, and remembered collapse state.
- **Ingress provenance.** Prototype phase 2 of [PROVENANCE.md](PROVENANCE.md), signed ingress receipts, against a Hypersnap fork, and show receipts when nodes publish them.
Expand Down
Loading
Loading