5.8 KiB
Design: Persistent Login + Unused Commands UI
Date: 2026-04-17 Status: Approved
Overview
Two features:
- Persistent login — users sign in once per machine; the session is restored on next app launch unless they explicitly sign out.
- Unused commands — wire
delete_message,update_profile, andadd_contactinto the frontend; currently these backend commands have no UI.
Feature 1: Persistent Login
Architecture
Use tauri-plugin-store (official Tauri v2 JSON store plugin) to persist the JWT token to disk in the app data directory.
Store file: session.json
Store key: "token" (string value)
Backend changes
Cargo.toml
- Add
tauri-plugin-store = "2"
lib.rs
- Register
.plugin(tauri_plugin_store::Builder::default().build())intauri::Builder
commands/user.rs
signin: addapp_handle: AppHandleparameter. After settingstate.token, open the store and write the token string under key"token".signup: same — after settingstate.token, write token to store.signout: addapp_handle: AppHandleparameter. Afterdb.invalidate(), open the store and delete the"token"key.- New command
restore_session: opens the store, reads"token". If absent → return error (frontend shows auth screen). If present → callstate.db.authenticate(jwt)with the token, then querySELECT * FROM $auth. If the query returns a user → updatestate.tokenand returnUser. If authenticate fails (expired/invalid) → delete the token from the store, clearstate.token, return error.
lib.rs invoke_handler
- Add
commands::user::restore_sessionto the handler list.
Frontend changes
src/routes/+page.svelte
init(): replacecmd<User>('get_me')withcmd<User>('restore_session'). Behaviour is identical from the frontend's perspective — success → app view, error → auth view.
No other frontend changes needed for this feature; token save/clear happens entirely on the Rust side inside the existing signin/signup/signout commands.
Data flow
App launch → init() → restore_session
├─ token on disk, valid → User returned → app view
├─ token on disk, expired → store cleared → error → auth view
└─ no token → error → auth view
signin/signup → token saved to store automatically
signout → token removed from store automatically
Feature 2: Unused Commands UI
2a. Delete Message
Backend: delete_message(message_id: String) already exists. Enforces WHERE author = $auth so only the author can delete.
src/routes/+page.svelte
- Add
deleteMessage(msgId: string): callscmd('delete_message', { messageId: msgId }), then filters the deleted message from localmessagesstate on success. - Pass
onDeleteMessage: (msgId: string) => voidprop toChatMain.
src/lib/components/ChatMain.svelte
- Accept
onDeleteMessageprop. - Message context menu: add
{ label: 'Delete message', action: () => onDeleteMessage(full(msg.id)) }item, only whenfull(msg.author) === full(user?.id)(own messages only — avoids confusing menu items for others' messages; backend already enforces the constraint server-side). ChatMainneedsuserprop passed from parent to perform the author check.
2b. Update Profile
Backend: update_profile(username?: String, avatar?: String) already exists. Returns updated User.
src/lib/components/Sidebar.svelte
- Add
showEditProfile: booleanlocal state (defaultfalse). - Clicking the user pill (name/avatar area) in the footer toggles
showEditProfile. - When open: render an inline edit form above the footer (same visual pattern as the new-room form — fade-in animation, small field + save button).
- Fields:
username(pre-filled with current value),avatar(pre-filled, optional URL). - Save button calls
onUpdateProfile({ username, avatar })callback. - Cancel button (or pressing Escape) closes the form without saving.
- Fields:
- Add
onUpdateProfile: (fields: { username?: string; avatar?: string }) => Promise<void>to Sidebar's Props interface.
src/routes/+page.svelte
- Add
updateProfile(fields)function: callscmd<User>('update_profile', fields)→ updatesuserstate with returned value. - Pass
onUpdateProfile={updateProfile}toSidebar.
2c. Add Contact
Backend: add_contact(user_id: String) already exists. Returns a Contact record.
src/lib/components/Sidebar.svelte
- Add
showAddContact: booleanlocal state (defaultfalse). - Add a "+"
icon-btnnext to the CONTACTS section label (always visible even when contacts list is empty) to toggleshowAddContact. - When open: render inline form (same pattern as new-room form) with a single text input labelled "user id".
- On submit: call
onAddContact(userId)callback, then close form. - Add
onAddContact: (userId: string) => Promise<void>to Sidebar's Props interface.
src/routes/+page.svelte
- Add
addContact(userId: string)function: callscmd('add_contact', { userId })→ on success callsget_contactsto refresh the contacts list and updatecontactsstate. - Pass
onAddContact={addContact}toSidebar.
Error handling
restore_session: any error (missing token, expired, network) → auth view. No toast/message needed — user just sees the login screen.deleteMessage: errors shown in existingerrstate variable (already displayed in channel header).updateProfile: errors surfaced inside the edit form (local error state in Sidebar).addContact: errors surfaced inside the add-contact form (local error state in Sidebar).
Out of scope
- User search (no backend command exists; add_contact uses raw user IDs for now)
- Token refresh / expiry detection during an active session
- Avatar image upload (update_profile accepts URL strings only)