Bindet die neuen AppView-Endpoints an: sechs IPC-Commands
(fetch_notifications, notification_count, mark_notifications_seen,
fetch_followers, fetch_following, fetch_thread) plus profile_get_by_did,
dazu die TS-Gegenstücke.
* NotificationsView: Liste mit Icon/Text je Art, Avatar, Vorschau des
subject_text, Cursor-Pagination; Klick auf eine Zeile mit Subject öffnet
den Thread. Beim Öffnen wird mit dem indexed_at der obersten Zeile als
Wasserzeichen quittiert.
* NavRail: Eintrag mit Unread-Badge, gepollt im vorhandenen 5s-Timer und
über den bestehenden Teardown-Pfad abgeräumt; der Poll pausiert, solange
die Liste offen ist.
* ProfileView: Follower- und Following-Zahlen sind jetzt Buttons und öffnen
die jeweilige Liste inline.
Navigiert wird über die DID, nicht über den Handle: für Actors, die weder
profiles noch posts kennen, liefert die AppView einen abgeschnittenen
Platzhalter im handle-Feld ("did:plc:abcd…"), und ein Klick darauf landete
in einem synthetischen Leerprofil. Die echte DID steht im DTO und wird jetzt
explizit durchgereicht — keine Heuristik auf das Platzhalter-Format.
Dabei aufgefallen und mitgefixt: ProfileView lud nur in onMount, obwohl die
Komponente bei Profil-zu-Profil-Navigation gemountet bleibt — Posts und
Zahlen des vorherigen Nutzers wären unter dem neuen Namen stehengeblieben.
Jetzt ein auf (did, handle) gekeyter Effekt.
Das Thread-Overlay hing im else-Zweig der leeren Timeline und wäre aus dem
Notifications-View unsichtbar gewesen; es ist jetzt ein Snippet, das beide
Views rendern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
UI half of the profile feature. Mirrors the previous three commits
so the user can browse and edit profiles.
* `<Avatar did cid name size>` — reusable avatar component.
Falls back to an initial-letter (or "?" when name is empty) circle
when `cid` is null. Resolves the blob through the standard PDS
fetch path so it works for any author whose PDS the client can
reach.
* `<UserProfileView handle on_thread_click current_user_did>` —
public profile page. Fetches `GET /api/profile/<handle>` on
mount, renders the avatar / display name / bio / counts /
posts. The "edit profile" button is gated on
`current_user_did === profile.did` so a user browsing
someone else's profile can't issue an unintended `setMyProfile`
against their own DID.
* `PostCard` now renders an inline `<Avatar>` + clickable handle
button that calls a new `on_handle_click` prop. The clickable
area replaces the previous dead `<a href>` (Tauri webviews
have no router).
* `App.svelte` adds an `openUserProfile(handle)` handler that
sets `selectedHandle` + `view = "user"` and mounts
`<UserProfileView>`.
* New Tauri commands `profile_get_record` / `profile_set` in
`lib.rs` + matching client helpers `getMyProfile` /
`setMyProfile` in `client.ts`. The set command sends camelCase
field names; the PDS endpoint (previous commit) round-trips them
through `#[serde(rename_all = "camelCase")]`.
* Empty-state UX for users with no profile yet (new account, or a
third-party-PDS author whose profile the AppView hasn't indexed
yet): both the current-user "profile" view and the public
"user" view render a hint ("// no profile yet — click 'edit
profile' to set one up." / "// no profile yet.") instead of a
blank bio box.
* NavRail / NavRailHarness `View` union extended with "user"
so the navigation prop type accepts the new view.
The `bind:current={view}` pattern in NavRail did not propagate
clicks to the parent's $state. Svelte 5's $bindable on this
runtime is flakey and on this particular build (Tauri 2.11 +
svelte 5.x) the setter was never invoked when a button was
clicked, so the view state stayed 'home' no matter which rail
button the user pressed. The user reported 'search tut sich nix
genau auch bei compose etc.'
Replace with explicit callback prop:
let { view = 'home', on_select } = $props();
onclick={() => on_select?.(item.id)}
The parent then mutates its own `view` rune directly via the
arrow function — Svelte tracks this unconditionally regardless of
runtime-specific bindable semantics.
Includes a vitest regression test that mounts a real Svelte
component harness (jsdom) and asserts that click events on each
rail button flip the parent's `view` and update the .active
class — so any future regression is caught in CI rather than at
the Tauri app window.