R8 the Mastodon API made real in decePub: featured tags, notify, languages, recommendations, notes, suggestions, the direct stream

A profile shows the hashtags an account features as chips opening their feed; under another account's header is what the
persona keeps of it for itself alone: for one it follows, a bell (follow with notify), the languages of its posts that
reach the home feed (follow with languages[]) and Recommend (endorsements, private here), and for any account a private
note saved on blur or with Save. Settings edits the persona's featured hashtags (added by name or from those used lately,
10 at most) and its reading preferences (/clientapi/avatar/private/preferences), which CascadingState keeps per persona
and the card applies to sensitive media and content warnings. A hashtag's tab features it on the profile. Explore has Who
to follow (/api/v2/suggestions, dismissed for good after asking); Administration has Staff picks. The Direct tab follows
the direct stream's conversation events; status notifications show the post. The follows CSV carries notify and languages.

Verified: Release build, 0 errors (the 3 warnings were there before). Not run against PrivaPub or in the pasture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-18 08:58:30 +02:00
1 parent 566b0e33ea
commit d33697e3fc
28 files changed
+1784 -52

No files matched your search

+24 -1
View File
@@ -6,7 +6,8 @@
compare PersonaId to reload what belongs to the persona. With PageSettings.SyncWithLogin the theme is the login's:
taken at sign-in and at start-up, saved there a moment after it changes here. A persona the staff suspended is never
acted as: it can't be chosen, and when PrivaPub refuses the current one a token (TokenAuthStateProvider.PersonaRefused)
the session moves to another persona of the login, or to none, and says why once. *@
the session moves to another persona of the login, or to none, and says why once. The current persona's reading
preferences (how its cards show media and content warnings) are read with it and kept here. *@
<CascadingValue IsFixed=false Value=this>
@ChildContent
</CascadingValue>
@@ -43,6 +44,12 @@
public string PersonaId { get; private set; }
/// <summary>That persona's Mastodon account, null until it is read.</summary>
public Account Persona { get; private set; }
/// <summary>How that persona reads what others post (media shown or hidden, content warnings open or folded), read when
/// it is acted as; PrivaPub's defaults until then, or when they can't be read. A change replaces the object, which the
/// cards' render key holds.</summary>
public ViewPersonaPreferences Preferences { get; private set; } = new();
/// <summary>Preferences.ExpandMedia as the cards use it.</summary>
public ExpandMedia ExpandMedia => ExpandMediaModes.Of(Preferences.ExpandMedia);
/// <summary>The root handles reports and moderates accounts and posts (its JWT's policies); PrivaPub checks it anyway.</summary>
public bool IsModerator { get; private set; }
/// <summary>The root administers the server (its JWT's policies); PrivaPub checks it anyway.</summary>
@@ -226,6 +233,7 @@
await AuthStateProvider.SetPersona(null, cancellationToken: cancellationToken);
PersonaId = null;
Persona = null;
Preferences = new();
IsSessionLoaded = true;
StateHasChanged();
return;
@@ -261,10 +269,22 @@
Persona = await AuthService.CurrentAccount(cancellationToken);
if (Persona?.Host is { } host)
InstanceService.SelfHost = host;
Preferences = await AuthService.PersonaPreferences(personaId, cancellationToken) ?? new() { AvatarId = personaId };
StateHasChanged();
return result;
}
/// <summary>The current persona's reading preferences as PrivaPub now keeps them, after Settings saved them; the cards
/// show them from their next render.</summary>
public void PreferencesChanged(ViewPersonaPreferences preferences)
{
if (preferences is null || preferences.AvatarId != PersonaId)
return;
Preferences = preferences;
StateHasChanged();
}
/// <summary>Reads the root's personas again, after one was made.</summary>
public async Task ReloadPersonas(CancellationToken cancellationToken = default)
{
@@ -293,6 +313,7 @@
await AuthStateProvider.SetPersona(null, cancellationToken: cancellationToken);
PersonaId = null;
Persona = null;
Preferences = new();
StateHasChanged();
return;
}
@@ -338,6 +359,7 @@
await AuthStateProvider.SetPersona(null, cancellationToken: cancellationToken);
PersonaId = null;
Persona = null;
Preferences = new();
Toast.AddWarning(suspended
? Localizer["{0} is suspended by this server's staff, and no other persona of this login can act now.", name]
: Localizer["{0} can no longer be used, and no other persona of this login can act now.", name]);
@@ -405,6 +427,7 @@
Personas = [];
PersonaId = null;
Persona = null;
Preferences = new();
}
async Task KeepSessionFresh(CancellationToken cancellationToken)