Commit Graph
235 Commits
Author SHA1 Message Date
Jannis Braun 42d1be54fa fix(web): guard RegistrationPanel invite-list against stale-fetch race 2026-04-29 00:57:57 +02:00
Jannis Braun dcd3daf390 feat(web): RegistrationPanel invite-list with Active/Archived tabs 2026-04-29 00:53:39 +02:00
Jannis Braun 7dd6bd6263 refactor(web): move registrationOpen toggle out of GeneralPanel 2026-04-29 00:44:35 +02:00
Jannis Braun cad6c50bff feat(web): RegistrationPanel scaffold with both registration toggles 2026-04-29 00:40:04 +02:00
Jannis Braun 76b6621d62 feat(storage): remove upload-size cap, add MB/GB unit toggle
Storage panel's max-upload-size input now accepts any positive integer
(bounded only by JS safe-integer ceiling) and offers an MB/GB unit
toggle. Multipart limit relaxed to MAX_SAFE_INTEGER — actual cap is
enforced per-request from the DB setting, not at the framework layer.
2026-04-28 18:37:16 +02:00
Jannis Braun 82ae487765 feat(sounds): add 'Play sound for every message' toggle to Voice settings 2026-04-28 14:50:41 +02:00
Jannis Braun 4748df8b24 fix(camera): dormant-by-default preview, never trigger camera on tab open
Voice & Video tab no longer fires getUserMedia on mount. Uses
navigator.permissions.query({name:'camera'}) for state detection,
which is passive (no LED activation). Preview only opens when the
user explicitly clicks the dormant tile or the prompt-state CTA.
In-call mode unchanged (attaches existing LK track, no extra LED).

Spec and plan updated to reflect the corrected design — the
"auto-start preview, no Test Camera toggle" rationale was wrong;
macOS holds the camera LED on for ~2s after release, so any
incidental getUserMedia call (probe, transient mount) flashes the
LED in the user's face even when they aren't on the Voice & Video
panel. Privacy-correct UX: never light the LED without an explicit
user action.
2026-04-27 21:13:58 +02:00
Jannis Braun 2bec5b9d7f feat(settings): rename Voice tab to 'Voice & Video' to reflect added video section 2026-04-27 20:55:42 +02:00
Jannis Braun 93e1478346 feat(voice-panel): mount Video section between Volume and Voice Processing 2026-04-27 20:47:19 +02:00
Jannis Braun 47653ab14a feat(video-section): tab-visibility cleanup and currently-using subline 2026-04-27 20:46:04 +02:00
Jannis Braun 37caf30b84 feat(video-section): in-call preview attaches to LK track
Two-mode preview: in-call attaches to the LiveKit local camera track
(no double-capture); pre-call uses getUserMedia. Modes transition
reactively on isCameraOn changes.
2026-04-27 20:43:40 +02:00
Jannis Braun 4c6bc9e822 feat(video-section): pre-call preview tile auto-starts getUserMedia 2026-04-27 20:41:03 +02:00
Jannis Braun 9b402ed291 feat(video-section): enumerate cameras and render dropdown
Empty labels fall back to 'Camera N'; duplicate labels are
disambiguated with ' (1)', ' (2)' suffixes by enumeration order.
'No cameras detected' subline appears when enumeration is empty.
2026-04-27 20:39:29 +02:00
Jannis Braun 5494460834 feat(video-section): scaffold component with permission-probe state machine 2026-04-27 20:35:29 +02:00
Jannis Braun 113efcf583 fix(web): drop optimistic updates and disable inert Start-minimized toggle
- AutoLaunchSettings: remove pre-await state writes; backend response is
  now the sole writer to openAtLogin/startMinimized state, eliminating
  the flicker caused when optimistic values diverged from OS truth
- Add busy flag across IPC round-trip; both Toggles receive disabled={busy}
- Start-minimized Toggle additionally receives disabled={!openAtLogin},
  preventing interaction when it would have no effect
- Toggle.tsx: add optional disabled prop (forwarded to <button disabled>)
  with cursor-not-allowed opacity-50 visual feedback; non-breaking for
  all existing callers
2026-04-27 12:57:34 +02:00
Jannis Braun cebbd5c859 feat(web): user-facing pending peering subscriptions and outcome notifications
Two new inline sections in the user-facing federation/connections settings
panel: 'Recent peering outcomes' (terminal-state notifications with
Retry-for-approved + Dismiss) and 'Pending peering approvals' (active
subscriber rows the user is waiting on, with Cancel). New WS handlers for
peering_subscription_changed and peering_notification_received refresh the
lists in real-time and surface a transient toast for online users. Retry
deep-link for friend_add prefills the friend-add input with the original
target handle (other reasons get Dismiss only — the gate doesn't wire
those paths yet).
2026-04-26 22:42:53 +02:00
Jannis Braun eddf2254cc feat(web): render outbound peering requests with subscriber list in admin panel
PendingApprovals now branches row rendering on direction. Inbound rows
render exactly as before. Outbound rows show '<instance> — N users
want us to peer' with an inline subscriber list (username + reason +
target). ConfirmDialog descriptions branch on direction so admins
see appropriate copy for outbound approve (initiate handshake on
behalf of N users) vs outbound deny (notify requesting users).
2026-04-26 22:35:16 +02:00
Jannis Braun fe969a7d96 fix(web): friend-add error toasts surface raw codes — read err.message, not err.body
The T19/T20 catch blocks looked for an `.body` property on thrown errors
to extract the structured error code. The shared API client (api/client.ts:298)
actually throws `new Error(body.error)` — the code lives in `err.message`,
and there's no `.body` attached.

Live E2E (T22 scenario 2) caught this: typing alice@orbit against an
awaiting_approval peer surfaced the raw code 'peer_pending_approval' as
the toast text instead of the human-readable mapServerErrorToMessage
output. Same defect would have hit every server-error toast on both
FriendsPage (AddFriend + UserDiscoverCard) and UserProfileModal.

Catch blocks now use err.message as both the code and the fallback text;
the inline comment points at the API client throw site so the contract
is documented at the consumer.
2026-04-25 23:17:19 +02:00
Jannis Braun 4a939b743f refactor(web): UserProfileModal — toast on server errors, drop ConnectInstanceModal triggers
Same pattern as FriendsPage cleanup (T19): the friend-add catch no longer
branches on the deleted InstanceNotConnectedError / InstanceDisconnectedError;
the ConnectInstanceModal trigger is removed since the server handles
all routing/peering. Errors surface via toast using mapServerErrorToMessage.

This restores the web package to a compileable state.
2026-04-25 22:35:52 +02:00
Jannis Braun 086158511c test(join-space): update stale assertions to match current UI and API
Four assertions drifted from the current JoinSpace modal, carried over
when the file was renamed from the old JoinServer component in fc06e25
without being updated:

- Placeholder was expanded to cover URL-form invite input
  ('e.g. abc123' → 'e.g. abc123 or https://instance.com/join/abc123').
- 'shows validation error when submitting empty code' asserted a code
  path that no longer exists: the submit button is now disabled when
  the trimmed input is empty (JoinSpace.tsx line 166), so clicking it
  is a no-op and the 'Invite code is required' error from the parser
  is unreachable from the rendered form. Replaced with an assertion
  that the button is disabled while the input is empty — the actual
  validation UX.
- joinByCode signature took on a second `origin` argument during the
  S2S DM unification + federated-join work (spaceStore.ts line 69).
  parseInviteInput returns { code, origin: undefined } for a bare
  code, so the call is `joinByCode('my-invite-code', undefined)`.
  Assertion updated to match exactly.

No code behavior change — tests now reflect actual behavior, which
was already correct and deployed. Closes backlog #28.
2026-04-23 02:52:00 +02:00
Jannis Braun 4d2e50b55b fix(web): extract cross-store resolvers into neutral utility to break TDZ
instanceStore registers three resolver functions at module load —
setApiForOriginResolver, setUserIdForOriginResolver,
setOriginFromHostnameResolver — whose backing `let` bindings used to
live in spaceStore. When the module graph was entered from
instanceStore (e.g. JoinSpaceModal importing useInstanceStore) the
order became spaceStore → chatStore → useWebSocket → socialStore →
instanceStore (top-level setter call) while spaceStore was still
paused on its line-8 chatStore import, so the backing `let` had not
been reached yet and the setter crashed with
`Cannot access '_getApiForOrigin' before initialization`. This left
InviteModal.test.tsx and JoinSpace.test.tsx unable to even load their
suites once AudioManager was mocked away.

Move the three `let` bindings, their setters, their pure getters, plus
the WS-populated user-ID cache (`_myUserIdByOrigin`, setMyUserIdForOrigin,
getCachedUserIdForOrigin, clearMyUserIdCache) into
`packages/web/src/utils/crossStoreResolvers.ts`. The utility imports
nothing from `./stores/*`, so no back-edge exists. spaceStore re-exports
the public surface for backward compatibility with the many existing
import sites; instanceStore imports the setters directly from the
utility (the in-cycle re-export path does not resolve at module-init
time under vite-ssr, so a direct import is required for the top-level
setter calls).

spaceStore's remaining wrappers (resolveUserOrigin, getLayoutHomeOrigin,
getMyUserIdForOrigin) stay where they are — they combine the utility's
pure lookups with authStore state — but now delegate to the utility.

Also adds the AudioManager mock to InviteModal.test.tsx and
JoinSpace.test.tsx so their suites actually load (same pattern already
used in 5 other test files). Net test-suite result: 127/131 pass (up
from 121/121 — +6 newly unlockable). The 4 remaining JoinSpace
failures are pre-existing stale UI-text assertions (the placeholder was
expanded and the submit button was made disable-when-empty) made
visible by the suite now loading; they're orthogonal to this change
and handed back for a separate triage.

Closes backlog #27.
2026-04-23 02:46:19 +02:00
Jannis Braun 5a1e354ae1 feat(federation-ui): add needs_attention pill and Reset peering action
- peerStatusLabel/Color/DotColor gain a 'needs_attention' case (rose).
- StatusFilter row gains 'Needs Attention' toggle.
- PeerRow hides Rotate/Revoke and shows 'Reset Peering' when status is
  needs_attention, plus an Auth Failures stat.
- Parent panel routes 'reset' through a ConfirmDialog (danger variant)
  that spells out the destructive nature and the out-of-band re-peer step.
- Client FederationPeer interface gains consecutiveAuthFailures (Task 2
  extended the shared type but the web client's local mirror was stale).

Codifies the manual 'delete both sides, re-peer' workaround as a
first-class admin action.
2026-04-21 21:02:49 +02:00
Jannis Braun 3d8709d20a feat: real-time Federation panel updates via WS events
Added federation_peers_changed (no-payload signal) broadcast from every
peer state mutation, and federation_approval_request_received when a new
approval request is queued. Client subscribes via onFederationPeersChanged
callback registry. FederationPanel and PendingApprovals debounce-refetch
on any event. sendToAdmins helper broadcasts only to admin users.
2026-04-20 18:28:10 +02:00
Jannis Braun 072858cbbb fix: multiple federation peering bugs
1. queueOutboxEvent no longer creates pending peer placeholders when
   autoAcceptPeering is disabled — prevents bypassing the admin's
   peering control

2. Approval endpoint checks for 202 before response.ok — when the
   remote also has autoAcceptPeering off, sets peer to awaiting_approval
   instead of incorrectly activating it

3. awaiting_approval status added to Federation panel UI — status label,
   colors, filter options so these peers are visible and manageable
2026-04-20 17:54:00 +02:00
Jannis Braun b0d3ee93b0 fix: restore approvalCount state variable in FederationPanel 2026-04-20 15:11:47 +02:00
Jannis Braun 9e3c411e80 feat: badge count on Federation tab for pending approval requests 2026-04-20 15:10:56 +02:00
Jannis Braun 975ef93cc0 feat: pending approval requests section in Federation panel 2026-04-20 15:10:07 +02:00
Jannis Braun b434a736a8 fix: address code review findings (C1, I1, I2)
- C1: Include 'unreachable' peers in queueOutboxEvent query to prevent
  UNIQUE constraint violation when creating placeholders
- I1: Add 'rejected' to StatusFilter in FederationPanel so admins can
  see and manage rejected peers with delete/re-initiate actions
- I2: Map ensurePeered 'failed' to 'pending' in peer/ensure response
  to match spec and client expectations
2026-04-09 14:08:54 +02:00
Jannis Braun 70a421864c feat: DM unreachable member indicators and admin auto-accept peering toggle 2026-04-09 13:58:45 +02:00
Jannis Braun a1c47e071e fix: prevent connecting to own instance as a remote
Add shared isSelfOrigin() helper that normalizes origins before
comparing to window.location.origin. Fixes auto-connect treating
self-referencing replicatedInstances entries as remote connections,
causing duplicate friends/DMs/data. Also hides self-referencing
entries from the Connections panel UI.
2026-04-08 01:28:20 +02:00
Jannis Braun 02a44c201d fix: clean up replicatedInstances and registry on federation identity deletion
After deleting a federated identity, the server-side user_federation_registry
and users.replicated_instances were not cleaned up, causing "already connected"
errors when trying to re-federate. The deletion endpoint now authoritatively
removes both the registry row and the replicatedInstances entry, and bumps the
LWW timestamp to prevent stale client syncs from re-inserting them.

Also extends the endpoint to accept mode 'leave' (skip S2S, just clean up),
and enables the "Select instances..." scope option in DeleteIdentityDialog.
2026-04-03 04:56:25 +02:00
Jannis Braun 1d757ed839 fix(web): update DeleteIdentityDialog descriptions to match actual behavior 2026-04-03 03:57:34 +02:00
Jannis Braun c918199cee feat: wire up DeleteIdentityDialog with S2S deletion modes and scope
Update the dialog to support three deletion modes (leave/soft/full),
scope selector with disabled "Select instances..." option, loading
state during deletion, and per-instance error handling via toasts.
2026-04-03 02:48:35 +02:00
Jannis Braun e7be58f01d feat: implement deleteIdentity with S2S relay for soft/full modes 2026-04-03 02:43:55 +02:00
Jannis Braun 54b7877472 fix(web): fetch members on mount in TransferOwnershipModal
The modal read from the global spaceStore.members which only contains
members for the currently active space. Opening the modal via right-click
context menu on a space that hasn't been navigated to yet resulted in an
empty member list. Now fetches members independently via the dedicated
GET /api/spaces/:id/members endpoint with federation-aware API routing.
2026-04-03 01:37:03 +02:00
Jannis Braun d0864f13b9 fix(web): use Avatar component in TransferOwnershipModal for proper fallback colors
The modal was manually rendering avatars with a static bg-surface-input
background, bypassing the Avatar component's getAvatarGradient() logic.
Users without profile pictures got blank dark circles instead of their
hash-generated or user-set avatar color gradients.
2026-04-03 01:27:51 +02:00
Jannis Braun a07f2314ed fix(web): standardize modal button layout across all dialog modals
Apply symmetrical full-width button pattern to:
- Delete Identity dialog (ConnectedInstances)
- Transfer Ownership confirmation
- Image Crop modal
2026-04-03 01:15:14 +02:00
Jannis Braun 839197d4b8 fix(web): add re-authenticate flow, safe URL parsing, and minor UI fixes 2026-04-01 18:23:27 +02:00
Jannis Braun 95e4dd9fa8 feat(web): overhaul ConnectedInstances with persistent registry list, filters, expandable rows, and delete identity dialog 2026-04-01 18:08:14 +02:00
Jannis Braun d7e2f25c31 refactor(web): rename removeInstance to disconnectInstance, wire up registry on disconnect 2026-04-01 18:00:08 +02:00
Jannis Braun 7f3ca4e6d1 feat(web): route DM creation to home instance with federated identity
All DM creation and add-member call sites now use the home api client
and pass homeUserId/homeInstance instead of routing to the remote instance.
Also updates addMember in the API client to accept AddDmMemberRequest.
2026-04-01 12:45:13 +02:00
Jannis Braun f55238f8e5 feat(web): wire up confirm dialogs and action handlers for peer management 2026-04-01 01:53:32 +02:00
Jannis Braun a91f7b5df1 feat(web): implement expandable peer details with stats, rotation, and actions 2026-04-01 01:41:11 +02:00
Jannis Braun 72dba1c03e feat(web): add peer list with filtering, sorting, and compact rows 2026-04-01 01:39:19 +02:00
Jannis Braun 74ed5c44ce feat(web): implement FederationPanel global settings section 2026-04-01 01:26:18 +02:00
Jannis Braun 5ca5db7122 refactor(web): extract federation section from GeneralPanel 2026-04-01 01:25:26 +02:00
Jannis Braun f49c2b5488 feat(web): register Federation tab in instance settings 2026-04-01 01:23:56 +02:00
Jannis Braun a3a7527c9e chore: add system docs, specs, and misc updates from other sessions
- Add complete docs/systems/ reference (18 system docs)
- Add federation relay status doc and prior spec/plan docs
- Remove superseded docs/federation-dm-s2s.md (replaced by docs/systems/federation.md)
- CLAUDE.md updates
- Minor fixes in social.ts, types.ts, AddDmMemberModal, NewDmModal, UserSettings
2026-03-31 03:40:34 +02:00
Jannis Braun becd5c8ac8 fix: exempt existing DM members from friend check in createGroup
When converting a 1-on-1 DM to a group, the existing DM partner was
incorrectly required to be your friend. DMs don't require friendship,
so this check was over-strict. Added fromDmChannelId parameter to
createGroup — existing members of the source DM are exempt from the
friendship validation.
2026-03-27 02:48:26 +01:00
Jannis Braun d11923f6a5 fix: remove unused import and guard URL parsing in AddDmMemberModal
Remove unused TaggedFriend type import. Wrap new URL() in try/catch
for federation origin display to handle malformed _instanceOrigin.
2026-03-27 02:32:13 +01:00