R4 the client catches up: follow requests, followers, downvotes, dismissing, filtering a post, quote policies, statistics in detail
Build / Build (push) Successful in 1m8s
Build / Build (push) Successful in 1m8s
Follow requests answered on the persona's own profile and from the Notifications tab; its followers and the accounts it follows listed there, a follower removed (asked first). Downvotes on threadiverse posts, after the public-vote notice. A notification dismissed from its line. "Filter this post" from a card's menu, and the Filters page showing the posts a filter holds. Who may quote the persona's own posts, and revoking a quote of one. Statistics: a server in detail with "describe again", the ledger's events, the server's own days. The composer deletes an upload taken out before posting. MediaType.Documents goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
1 parent
b731d8c8c0
commit
b8d7858acb
25 files changed
+1543
-61
No files matched your search
@@ -159,16 +159,16 @@ Below `md` the app is laid out for a phone, and a touch screen gets thumb-sized
|
||||
navigation, which closes every overlay and goes on.
|
||||
- No history entries are pushed.
|
||||
- **What can't be undone asks first** through `Services/ConfirmService.cs` (`Ask`, shown by `ConfirmHost` in
|
||||
`MainLayout`): deleting a post, taking it off a wall, muting, blocking, leaving a group, dropping a scheduled post and
|
||||
signing out.
|
||||
`MainLayout`): deleting a post, taking it off a wall, muting, blocking, removing a follower, revoking a quote, leaving a
|
||||
group, dropping a scheduled post and signing out.
|
||||
- **On a touch screen:**
|
||||
- a tap on a post opens its thread (`main.js`, `data-thread` on the card, its `data-thread-link`), and cards are
|
||||
pinned to the globe with a mouse only;
|
||||
- a tap on a nerd-stats trigger opens its rows in a sheet (`NerdTip`, `AppStatusService.CanHover`);
|
||||
- pictures open in `Components/MediaViewer.razor`.
|
||||
- **The feed on a phone:**
|
||||
- a card's actions are five equal cells (Reply, Boost, Favourite, React, More). Bookmark, the thread and the nerd
|
||||
stats' Details are in More, and the author's picture is in the header;
|
||||
- a card's actions are five equal cells (Reply, Boost, Favourite, React, More). Bookmark, a downvote, the thread and
|
||||
the nerd stats' Details are in More, and the author's picture is in the header;
|
||||
- a new post starts from a floating button;
|
||||
- the globe's banner opens full screen, and a phone held sideways opens it full screen from the tabs;
|
||||
- the feed tabs show their names and fade at the edge where more wait (`edge-fade-x`, `data-scroll-x`).
|
||||
@@ -288,24 +288,31 @@ Never redeclare the base's members in a component.
|
||||
- **Services** (`Services/`): `TimelineService` (every feed as `FeedPage`s of `FeedPost`s, paged by `max_id`/`min_id`;
|
||||
circles are read as ids from `/clientapi` and hydrated through `/api/v1/statuses?id[]=`; located posts come from
|
||||
`/clientapi/post/nearby` and have no Mastodon actions), `StatusService` (publish, favourite, boost, bookmark, delete,
|
||||
threads, mute, block, poll votes, emoji reactions through Pleroma's `/api/v1/pleroma/statuses/:id/reactions`, joining and
|
||||
threads, mute, block, poll votes, downvotes, who may quote the persona's posts and revoking a quote of one, emoji
|
||||
reactions through Pleroma's `/api/v1/pleroma/statuses/:id/reactions`, joining and
|
||||
leaving remote events through `/api/privapub/v1/statuses/:id/join|leave`; located and group posts go through
|
||||
`/clientapi/post/insert`, and a located post's author deletes it through `/clientapi/post/delete`, its card's one action), `AccountService` (profiles, relationships, follow,
|
||||
block, mute, follow requests, `/api/v2/search`, whose handles and URLs PrivaPub resolves), `ListService` (the
|
||||
block, mute, follow requests, the persona's followers and following and removing a follower, `/api/v2/search`, whose
|
||||
handles and URLs PrivaPub resolves), `ListService` (the
|
||||
persona's lists and their members; members are only accounts it follows, found with `accounts/search?following=true`),
|
||||
`FilterService` (the persona's v2 filters, their keywords and filtered posts), `DiscoveryService` (hashtags and
|
||||
following them, trends, the directory), `PortabilityService` (Mastodon's CSV exports and imports), `AdminService`
|
||||
(`/clientapi/moderator` and `/clientapi/admin`), `MediaService` (uploads), `GroupService`, `InstanceService` and
|
||||
(`/clientapi/moderator` and `/clientapi/admin`), `MediaService` (uploads, and deleting those no post holds),
|
||||
`GroupService`, `InstanceService` and
|
||||
`ProvenanceService`.
|
||||
- **Reactions and votes are public** (owner decision, PrivaPub's ROADMAP): before a persona's first reaction or poll
|
||||
vote, `PublicNoticeService` asks it once (`PublicNoticeHost` in `MainLayout`) and remembers the persona in
|
||||
- **Reactions and votes are public** (owner decision, PrivaPub's ROADMAP): before a persona's first reaction, poll vote
|
||||
or downvote, `PublicNoticeService` asks it once (`PublicNoticeHost` in `MainLayout`) and remembers the persona in
|
||||
localStorage (`PublicActionNotices`). Anything else that makes a persona's act visible to others gets the same notice,
|
||||
with its own `PublicAct`: joining an event (owner decision 2026-10-05) is asked once more, since the event's server
|
||||
shows who takes part. An event card shows the persona's participation (`privapub.event.participation`: pending,
|
||||
accepted, rejected) with Join or Leave, except for events by invitation or taken on another site (`join_mode`).
|
||||
- **What a card shows of other platforms:** custom emoji (`Helpers/CustomEmojis.cs`: a post's or account's `:shortcode:`
|
||||
in its text, name, warning, bio and fields, as the picture the server listed, never inside a tag), a threadiverse
|
||||
post's downvotes next to its favourites, blurhash placeholders until a picture loads (`wwwroot/js/blurhash.js`
|
||||
post's downvotes next to its favourites with the persona's own as a toggle (`data-downvote`, in More on a phone as
|
||||
`data-downvote-phone`; `StatusAction.Downvote`/`Undownvote`, PrivaPub's `/statuses/:id/downvote|undownvote`, answered
|
||||
with the post, its `privapub.votes.downvoted` set and its favourite dropped). PrivaPub sends votes on every post, so
|
||||
the card offers the toggle where they mean something (`Content.CanDownvote`: a threadiverse server's post, a Page, or
|
||||
one with downvotes; never the persona's own). It also shows blurhash placeholders until a picture loads (`wwwroot/js/blurhash.js`
|
||||
paints any `[data-blurhash]`: a picture's until it loads, again when its source changes, and for good behind the
|
||||
sensitive-media button), a picture's focal point (`object-position`), and an event's online link, participants and
|
||||
status. A picture, video or sound that fails to load says "Media unavailable" with "Open original"
|
||||
@@ -319,6 +326,12 @@ Never redeclare the base's members in a component.
|
||||
Another's profile and posts can be reported (`Components/ReportDialog.razor`, `/api/v1/reports`): a category, words,
|
||||
and for a remote account a copy to its server, which PrivaPub sends from its own actor, never naming the persona.
|
||||
Who liked or boosted a post opens from its card's menu (`Components/InteractedByDialog.razor`).
|
||||
On the persona's own profile its counts are buttons (`data-profile-following`, `data-profile-followers`) opening the
|
||||
accounts it follows and its followers (`Components/AccountListDialog.razor`, 40 a page by the Link header): PrivaPub
|
||||
lists them to the persona alone (`hide_collections`), so another profile keeps plain counts. A follower is removed
|
||||
there (`remove_from_followers`, `data-follower-remove`, asked first). The follow requests waiting for the persona show
|
||||
above its posts (`Components/FollowRequestsPanel.razor`, `data-follow-request`, accepted or refused; hidden while none
|
||||
waits), and the Notifications tab opens the same panel in a sheet while some wait (`data-follow-requests-open`).
|
||||
- **Walls** (Smithereen's, FEP-400e): a post written on someone's wall says whose (`privapub.wall`, `data-wall`, linking
|
||||
that profile); one written on the persona's own wall is taken off from the card's menu (`DELETE` on the status, which
|
||||
PrivaPub turns into a `Remove` that Smithereen takes for a deletion). Only the persona's followers may write there.
|
||||
@@ -334,7 +347,11 @@ Never redeclare the base's members in a component.
|
||||
matches (`Status.Filtered`), whatever their context; a page tells each card where it is (`Content.FilterIn`: home and
|
||||
lists, notifications, public feeds, threads around the opened post, profiles; none for favourites, bookmarks,
|
||||
conversations and search), and the card leaves the post out (hide), folds it behind the filter's title (warn) or blurs
|
||||
its media (blur).
|
||||
its media (blur). Another's post goes into a filter from its card's menu ("Filter this post", `data-filter-post`,
|
||||
`Components/FilterPostDialog.razor`: `POST /api/v2/filters/:id/statuses`, a filter that holds it already says so), and
|
||||
the card reads the post again so the filter applies at once. On /filters the posts a filter holds are read by id when
|
||||
it opens (`TimelineService.Read`, `/api/v1/statuses?id[]=`) and show their author and words, linking their thread
|
||||
(`data-filter-status`), or say "Post no longer available" (`data-filter-status-missing`).
|
||||
- **Articles** fold in a feed (`Content.IsFoldedArticle`): an Article or a Page with its title, or a titled post longer
|
||||
than a few paragraphs (WriteFreely sends Notes with a name), shows its cover, its excerpt and a Read button; the
|
||||
post opened in its thread shows whole.
|
||||
@@ -347,7 +364,9 @@ Never redeclare the base's members in a component.
|
||||
- audio and video PrivaPub answers 202 for are followed (`GET /api/v1/media/:id`, 206 until ready);
|
||||
- the ids are kept, so a post that fails never uploads its media again;
|
||||
- the types and sizes accepted are PrivaPub's own (`/api/v2/instance`, `MediaService.Limits`);
|
||||
- a description changed after the upload is PUT before the post.
|
||||
- a description changed after the upload is PUT before the post;
|
||||
- a file taken out before posting is deleted from PrivaPub (`MediaService.Discard`, `DELETE /api/v1/media/:id`,
|
||||
`data-upload-remove`), unless a post holds it already (an edit's own media); a failure is only logged.
|
||||
- **Alt text:** publishing a picture without a description asks once (`data-alt-reminder`, "Publish anyway"), unless
|
||||
the reader turned `PageSettings.RemindAltText` off in Settings → General.
|
||||
- **Edits send the whole post, as Mastodon wants it:** its words and warning from `/source`, its own language and
|
||||
@@ -364,6 +383,12 @@ Never redeclare the base's members in a component.
|
||||
- **Interaction policies** (GoToSocial's, and FEP-5624's `canReply` on a PeerTube video whose comments are moderated): a
|
||||
reply or boost whose target's author must approve it shows so on its card (`privapub.approval`: pending, then nothing
|
||||
once approved, or rejected); one the policy shuts out is refused by PrivaPub (422) and shown as an error.
|
||||
- **Quotes of the persona's posts** (FEP-044f): its own public and unlisted posts are given who may quote them from the
|
||||
card's menu ("Who may quote it", `data-quote-policy` holding the current one; `Components/QuotePolicyDialog.razor`,
|
||||
`data-quote-policy-choice`: anyone, its followers or nobody, `PUT /api/v1/statuses/:id/interaction_policy` with
|
||||
`quote_approval_policy`; the policy is read back from `quote_approval.automatic`, `StatusService.QuotePolicyOf`). A
|
||||
post by someone else quoting one of them has "Revoke quote" in its menu (`data-revoke-quote`, asked first; `POST
|
||||
/api/v1/statuses/:quoted/quotes/:quoting/revoke`, answered with the quoting post, its quote revoked).
|
||||
- **Settings** (`Pages/Settings.razor`, `Components/Settings/`): the login itself (`AccountEditor`: its email address
|
||||
(`/clientapi/user/account`), set only with the password and a 6-digit code mailed to the new address (`/user/email`,
|
||||
then `/email/verify` through `DOtp`; the old `/user/update` is refused), shown verified or not and with an address
|
||||
@@ -390,6 +415,17 @@ Never redeclare the base's members in a component.
|
||||
- **Administration** (`Pages/Administration.razor`, `Components/Admin/`, for moderators): reports, then for
|
||||
administrators the servers blocked for everyone, the federation statistics and the backups. PrivaPub has no admin list
|
||||
of users, and adding an admin endpoint waits for the owner, so the Users section stays empty.
|
||||
- **Statistics** (`Components/Admin/StatisticsPanel.razor`, `/clientapi/admin/statistics`, typed in ClientModels'
|
||||
`Admin/ViewStatistics.cs`): the period's overview, the servers (by traffic, failures, latency or name), the crawler, the
|
||||
ledger's events and the server's own days. Counted per server, never per account.
|
||||
- A server's name opens `HostDetailDialog` (`data-statistics-host-open`, `/hosts/{host}?days=`): what it says of itself
|
||||
as nerd rows by their field names, its days with the main counters (received, in, out and failures, summed from the
|
||||
ledger's `channel:…:…:outcome` keys like the servers table; `live` for a day not rolled up yet), its last events, and
|
||||
"Describe again" (`data-host-describe`, `POST /hosts/{host}/describe`, a queued job PrivaPub answers 202).
|
||||
- Events (`data-statistics-events`, `EventsTable`): by server, channel and outcome as the ledger keys them, 100 at a
|
||||
time, and "Load more" asks for those `before` the last one's time.
|
||||
- The server's days (`data-statistics-server`, `/server?days=`): its client API's requests by status class, the
|
||||
documents it served, and its ledger written, dropped and failed.
|
||||
- **Backups** (`Components/Admin/BackupsPanel.razor`, `/clientapi/admin/backups`; owner decision 2026-10-07): the server's
|
||||
backups (nightly, before each deploy, made here, uploaded), with what runs and the last restore's report.
|
||||
- A download asks for the password and gives a link good for ten minutes, a plain anchor, so the browser saves the tar
|
||||
@@ -402,7 +438,9 @@ Never redeclare the base's members in a component.
|
||||
what notifications from people the persona does not follow, people who do not follow it, new accounts, private
|
||||
mentions it did not ask for and limited accounts become (accept, filter, drop; `/api/v2/notifications/policy`), and the
|
||||
filtered ones, one request per account, let in (with that account from then on) or dismissed. The Notifications tab
|
||||
links there while requests wait.
|
||||
links there while requests wait. A notification is dismissed from its line (`data-notification-dismiss`,
|
||||
`TimelineService.DismissNotification`: `/api/v2/notifications/:group_key/dismiss`, its whole group, `ungrouped-{id}` for
|
||||
one alone), and the page takes that item out (`Content.OnDismissed`).
|
||||
- **Scheduled posts** (`Pages/Scheduled.razor` at `/scheduled`): the composer's clock button gives a Mastodon API post a
|
||||
time (`scheduled_at`, at least five minutes ahead; located and group posts have none); the page moves or drops them.
|
||||
- **Explore** (`Pages/Explore.razor` at `/explore`): trending hashtags, posts and links (PrivaPub's, from public posts
|
||||
|
||||
Reference in new issue
Block a user