ff55d9d486a302bf1288eac19b72b50025046716
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ff55d9d486 |
docs: plan desktop features and diagnose the Spotify sync bugs
CI / Build & test (Node 20) (push) Waiting to run
CI / Build & test (Node 24) (push) Waiting to run
CI / Build & test (push) Blocked by required conditions
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
Security / Secret scan (gitleaks) (push) Waiting to run
Security / Dependency scan (OSV-Scanner) (push) Waiting to run
Security / IaC/config scan (Trivy) (push) Waiting to run
Security / License compliance scan (Trivy) (push) Waiting to run
|
||
|
|
e58021408c |
docs: record the Electron window.prompt soundboard bug
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
2afe3453f0 |
docs: record four approved features not yet started
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
e89966435a |
fix(soundboard): read MANAGE_SPACE from the space bitfield
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
The add-sound control was gated on channelPermissions, which carries the per-channel bitfield; MANAGE_SPACE lives in the space-level one. The check silently evaluated false for everybody, including owners, so the button never rendered and there was no way to add a sound at all. Also raise the clip cap to 2 MB — a 30-second clip at a high bitrate cleared 1 MB — and stop reporting every upload failure as 'too large', which sent people to shrink a file that was not the problem. |
||
|
|
f5451e1b14 |
feat: soundboard, account menu, and call timer
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Soundboard: the trigger travels over the WebSocket and every client in the call plays the clip locally, instead of mixing it into the presser's microphone or publishing a LiveKit track. No upstream bandwidth, no media stack changes, and the clip is not degraded by voice processing. Fan-out uses a new sendToRoomParticipants rather than sendToRoom: the latter broadcasts a space room to the whole space, which is right for the presence the sidebar shows and wrong for anything audible. The cooldown is enforced server-side — a client-side one only slows down people not trying to abuse it, and a soundboard is the easiest thing here to turn into a weapon. Playing is open to anyone in the call; deciding what the buttons are needs MANAGE_SPACE. Account menu: the name in the user bar had cursor-pointer and no handler, so the interface was already promising a click that did nothing. Offers profile, status and copy-id — not the Clips or account switching the reference design shows, which would be dead UI here. Call timer: startedAt comes from the server, so a late joiner sees the call's age rather than their own arrival. Empty space rooms are destroyed already, which is what makes the next call start from zero — no reset logic needed. |
||
|
|
ef5545465d |
docs: mark GIF favourites, audit log and statistics delivered
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
1830051732 |
feat(stats): voice-time and message leaderboards per space
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Voice stays get their own table rather than joining the audit log: that table records points in time, a call is an interval, and pairing join/leave point events would leave every query guessing at joins whose leave never arrived. Sessions are opened and closed inside joinRoom/leaveCurrentRoom rather than at the seven call sites that reach them, so no path can be missed, and destroyRoom closes them too — it bypasses leaveCurrentRoom and would otherwise leak open rows. A restart leaves sessions open with an unknowable end time. They are closed at startedAt, discarding that time rather than inventing it: crediting the gap would hand someone hours they never spent, and the numbers are the point. Mirrors the existing users.status sweep on boot. Only closed sessions count, so a figure does not move on every refresh. Bars scale to the leader, not the total — with five people every share of a total looks identical. Statistics are readable by any member, since they are the group's own numbers; the audit log, which names who did what, stays on MANAGE_SPACE. |
||
|
|
bbb190cbda |
feat(audit): append-only audit log for spaces
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Records who changed what, and is the mechanism statistics will read — one event table rather than two logs that drift apart. The table is deliberately generic (action + target + JSON metadata) so a new action needs no migration. Writes never throw: a kick must not fail because its log entry could not be written, since the kick already happened. Leaving is recorded as a different action from being removed. The same route serves both, and a log that conflates them misleads exactly when it matters. Actor is nullable with ON DELETE SET NULL: the event outlives the account, and a log that vanished with its actor would be worthless. Reads are gated on MANAGE_SPACE rather than a new permission bit, which would default to nobody until every role was re-edited. Paging uses the snowflake id, stable even for two events in the same millisecond, and an action this build does not know still renders a row. |
||
|
|
fb662bfe12 |
feat(gif): favourites and category shortcuts
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Favourites are stored server-side per user, so one made on the phone is there on the desktop — the point of favouriting. The whole result is stored rather than an id: the provider offers no lookup by id, so an id-only favourite could not be rendered without re-finding it through search. Category chips translate their label but not their query, which goes to a provider that indexes in English. The star sits beside the tile button rather than inside it: a button within a button is invalid and swallows the click. Toggling is optimistic and reverts on failure, and favourites skip both the loading skeleton and the infinite scroll, which belong to provider-backed browsing only. Server caps favourites per user and rejects non-http(s) URLs, which become <img src> in everyone's picker. |
||
|
|
0fc6abeb6e |
docs: mark Spotify delivered, drop resolved items
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
75316b0882 |
feat(spotify): show the current track as an activity
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OAuth Authorization Code flow, with tokens kept server-side: refreshing needs the client secret, so the browser never holds a Spotify token — it asks this instance what is playing and this instance calls Spotify. The callback arrives as a plain browser redirect with no Authorization header, so the OAuth state carries the user id signed with the instance secret and is compared in constant time; without that, anyone could bind their Spotify account to another user. Activities are now tracked per producer. pushActivities replaced the whole list, so the desktop game detector and Spotify would erase each other — losing exactly the case this is for, a game and Spotify at once. Polling backs off when the tab is hidden and keeps the last known track on a network error rather than reporting 'stopped listening'. A rejected refresh token (access revoked on Spotify's side) drops the row so the UI stops claiming a live connection. Scope is read-only: user-read-currently-playing and user-read-playback-state. Per the fork's language rule, the new UI ships in en and pt-BR, and this round also translates the privacy panel. |
||
|
|
c022f2795f |
docs: record the i18n foundation and translation queue
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
688a1335cb |
feat(i18n): language foundation with en and pt-BR
Nothing in the project was translatable — every string sat inline in English. en.ts is the source dictionary and its type is derived from it, so a typo or a missing key fails typecheck instead of rendering the raw key at runtime. pt-BR.ts is deliberately Partial: translation proceeds one system per update and anything absent falls back to English, so a half-migrated interface is never broken, only partly English. Locale is persisted, guessed from the browser on first run, and kept in sync with <html lang> through a subscription — persisted state rehydrates after first paint, so a one-off assignment would miss it. Translates the voice input panel (including the mic test shipped earlier today) and the profile card as this round's system. Language options are labelled in the active language, so a wrong pick can always be undone. |
||
|
|
89e13441c8 |
docs: record the pt-BR language rule and new system ideas
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
63afd2fc89 |
fix(profile): hoist the activity hook above the early return
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
The activity selector sat below `if (!isOpen || !user) return null`. `user` arrives asynchronously, so the hook ran on some renders and not others; React counts hooks per render and tore the tree down with error #310 as soon as a profile finished loading. Move it above the guard and let the selector tolerate a null user. Typecheck and the suite both passed with the bug in place — TypeScript cannot see hook order and nothing renders this modal across the null-to-loaded transition. |
||
|
|
d80de49768 |
chore(caddy): serve the Gitea site alongside the app
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
The git server runs as its own stack in /opt/gitea and is reached by container name over the app's internal network, publishing no host port of its own. Only the reverse-proxy entry belongs here, where the Caddyfile lives. |
||
|
|
b92a0d837e |
docs: update roadmap after mic test and activity work
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
d7da0ff203 |
feat(activity): show the current activity on the profile card
The activity pipeline was already complete end to end — Activity type, store, WS broadcast, server validation, presence relay, and an ActivityCard used by four list surfaces — but the profile card rendered none of it, which is the 'Listening to Spotify' block the design calls for. Add ProfileActivity: richer than ActivityCard because the card has room for artwork, track and artist, so it reads details/state/assets. All optional, so it degrades to the bare name that today's process-based detector supplies. Also scheme-check activity image assets server-side. activity.url was already restricted to http(s) but assets.largeImage/smallImage were only length-checked — an asymmetry that was harmless while nothing rendered them, and is not once they become <img src>: a client could point them at a host it controls and harvest the IP of everyone opening that profile. |
||
|
|
bfe62d7078 |
feat(voice): mic test with loopback in voice settings
The level meter only measured a stream a call had already opened, so settings offered no way to check a mic before joining — the panel said as much. Add startMicTest/stopMicTest on AudioManager: the processed input bus is routed to the master output through a dedicated gain node, so the loopback can be disconnected precisely. Settings had deliberately never opened the mic itself; a mic test cannot honour that, so the test hands the mic back when it stops. Releasing needs two independent guards, because the user may join a call mid-test: AudioManager only stops the exact stream it opened (identity check, not a flag), and the caller must consent — the UI reads the call state, which AudioManager cannot, as it does not import stores. Unmounting mid-test tears the loopback down too. |
||
|
|
37407a5ecd |
feat(voice): open the profile card from voice participants
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
The profile popout already existed and was reachable from eleven places — messages, mentions, avatars, member list, DMs, activity panel — but no voice surface opened it, so clicking someone during a call did nothing. Wire it into the voice user rows (VoiceChannel's sidebar list) and the name label on grid tiles, whose avatar was already a ProfileAvatar; the name beside it not reacting read as the click failing. Left mobile alone deliberately: MobileSpacesScreen already opens the profile from its row wrapper, and MobileVoiceJoinSheet would layer a history-pushed full-screen profile inside a bottom sheet, which cannot be verified here. |
||
|
|
cad3867027 |
docs: add fork roadmap
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
|
||
|
|
20526e1bc8 |
feat(gif): outlined GIF glyph, and GIF picker for the profile banner
The composer's GIF button drew a filled rounded rect with the letters knocked
out, which reads as a solid square rather than a picker. Invert it: stroked
outline with filled letters, reusing the original glyph paths scaled to centre.
Banners already accept absolute URLs on both ends (server isValidAssetUrl
allows http(s); the profile render branches on banner.startsWith('http')), so
the picker stores the remote URL directly with no upload path. Previews can now
hold either a blob: or an https: URL, so revoking is guarded — calling
revokeObjectURL on a remote URL is a silent no-op that would hide a mistake.
|
||
|
|
c70b0095a9 |
feat(voice): jump to the call from the voice panel
The channel name under 'Voice Connected' was a plain div. Making it navigate needed more than an onClick: voiceStore never recorded which space the call was in, and spaceStore.channels only holds the space currently being viewed — so after navigating away the call's channel was unresolvable, which is also why the label degraded to a generic 'Voice Channel'. Capture space and channel name at join time (the only moment they are reliable) and use them for both the label and the jump. Covers space calls and DM calls. |
||
|
|
08db5374cb |
build: compile better-sqlite3 from source (no Node 20 prebuilt)
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
better-sqlite3@12.11.1 ships prebuilt binaries for ABI 127/137/141/147 only; Node 20 is ABI 115, so prebuild-install falls back to node-gyp, which fails on node:20-slim for lack of python3/make/g++. Add the toolchain to the builder stage, and in the runtime stage install, use and purge it inside a single layer so the final image ships no compiler. |