One sign-in for both of PrivaPub's APIs: the root logs in on /clientapi,
picks a persona, and each persona gets its own Mastodon token in
exchange for the JWT (PersonaExchange). HttpService picks credentials
and JSON by route (JWT and camelCase for /clientapi, the persona token
and snake_case for /api, nothing for /oauth), retries a refused persona
token once, and records every request for the nerd stats.
The feed page is three columns: navigation with a persona switcher, the
feed with a tab per kind (home, local, federated, nearby, communities
and circles, direct, notifications, favourites, bookmarks, hashtags),
and a 3D globe that looks at the spherical mean of the posts in view
and draws a pulse and an arc to a hovered one. Posts sit at their own
place, their event's venue, the reader (located posts) or their
author's server as PrivaPub publishes it. Cards favourite, boost,
bookmark, reply, mute, block and delete; the composer posts with a
content warning, visibility, media and alt text, or as a located post.
Older pages load as the end nears; newer ones are polled while visible.
Nerd stats in tooltips: provenance, author and server, place, media,
counts, paging, the globe's camera and frame rate.
The mock (Faker, MessagesService, the Message models and store) and the
OIDC template leftovers are gone. Strings in English and Italian.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
Ported from the owner's b2b.next and adapted from MudBlazor to the daisyUI
components:
- LocalizableComponentBase provides CascadingState, Navigation, Toast,
IsLoading/IsDefaultDisabled, WhenOnline/StartOnlinePolling, and after-render
jobs that actually run.
- CascadingState starts with initialised state and polls the online status
with a PeriodicTimer. It reports errors through ILoggingService plus a toast,
and disposes what it holds.
- New pieces:
- ToastService + ToastHost, the snackbar, with AddResponseError/Success/
Warning/Exception in SUtility;
- ErrorViewer, which shows the validation messages plus AdditionalError(s);
- LoggingService, which writes to the console and the IndexedDB client log.
- HttpService is rebuilt and registered:
- auth levels Get/Post/Delete, *Anon and *TotallyAnon;
- retry with backoff;
- classified failures (FailureCodes) instead of exceptions;
- HTTP 410 on cancel;
- the "default" client.
ReadWebResult and DefaultReadFromJsonAsync read its responses.
- ClientJsonContext is source-generated, and SUtility.DefaultSerializer and
Blazored.LocalStorage use it.
- WebResult gains a C# 14 IsInvalid extension property.
The code follows the imported rules:
- Every service and async helper takes a CancellationToken last.
- Primary constructors replace constructor-plus-fields.
- MessagesService and IStorage return Task<WebResult> / Task<List<T>>.
MessagesService is still mock, building its results in memory; the seed
feed and thread moved to Faker.
- Index and ExpandMessage follow the page pattern: an offline guard with
WhenOnline, LoadData, and early returns with toasts on the results they used
to ignore. ExpandMessage reloads on route change and shows "not found"
instead of a null message.
- Model and state collections are [], and the fake identity defaults moved
out of the models.
- Components:
- no ?. chains over initialised state;
- [Inject] instead of @inject;
- IsDefaultDisabled on the actions;
- autocomplete on every text input;
- Localizer[key, args] instead of string.Format;
- the localizer no longer leaks its inner _pLocalizer.
- Settings → General gains a language picker (English/Italiano), and
SetDefaultCulture limits the culture to those two.
Bugs fixed along the way:
- A compose with untouched (null) content passed the empty check.
- File errors were joined with <br/> and rendered as markup.
- ClientLogs.ErrorMessage() threw on warnings.
- CoalescingStringLocalizer[key, args] showed a raw "{0}" for missing keys.
- AppStatusService exposed a call to a JS function that doesn't exist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
The project referenced ../SocialPub/SocialPub/SocialPub.ClientModels, a path
that has not existed since the server became PrivaPub; it references
../SocialPub/PrivaPub.ClientModels now, and CI clones SocialPub beside it.
Retargeted to net10.0 with the Microsoft packages at 10.0.9.
BlazorDownloadFileFast moves to 1.0.0.1 because 0.2.0 used
IJSUnmarshalledRuntime, which .NET 9 removed. The Visual Studio bundler and
web compiler packages are gone; the bundle they produced, css/style.min.css,
is committed and stays the stylesheet, and its stale .gz is deleted because
.NET's static-asset compression writes that path itself.
AppConfiguration.ApiBaseAddress (https://privapub.thepra.dev) is the API
client's base address; the template's placeholder OIDC section and the
Toolbelt.Blazor.HotKeys script tag, whose package is not referenced, are
removed. The vhost sends noindex while the feed shows the prototype's mock
posts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB