Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
244 commits
Select commit Hold shift + click to select a range
d42d6a5
fix(media): stop off-screen media from loading itself
matheusfillipe Aug 30, 2026
952ba25
fix(irc): wait for sasl when the server splits its cap ack over two l…
matheusfillipe Aug 30, 2026
2f4c56d
fix(media): keep lazy images rendered so they actually load
matheusfillipe Aug 30, 2026
ee83e36
feat(palette): add the network log to quick actions
matheusfillipe Aug 30, 2026
e5693fd
feat(chat): make channel names in messages clickable
matheusfillipe Aug 30, 2026
ab2c7d5
chore: merge main into the branch
matheusfillipe Sep 2, 2026
f1c1e4b
chore(android): drop committed gradle output of a removed plugin
matheusfillipe Sep 2, 2026
c1d7657
feat(voice): redesign the voice and stream channel view, keep calls a…
matheusfillipe Sep 5, 2026
9af4577
feat(palette): open voice and stream channels from ctrl+k
matheusfillipe Sep 6, 2026
ed84ca4
fix(chat): drop the attachment link from a reply preview that shows t…
matheusfillipe Sep 6, 2026
aa4a6e9
feat(voice): put the call controls on the video, return to the previo…
matheusfillipe Sep 6, 2026
5edca69
feat(voice): release the mic when a call ends, add a local echo bot f…
matheusfillipe Sep 7, 2026
3913fbc
fix(i18n): stop the back camera button borrowing the go-back translation
matheusfillipe Sep 11, 2026
7144723
feat(media): record voice, video and screen messages, share location …
matheusfillipe Sep 11, 2026
72f301f
fix(media): play a video preview on first click, contain player crashes
matheusfillipe Sep 11, 2026
2d6fee6
feat(media): camera page for photos and videos, draggable location map
matheusfillipe Sep 18, 2026
6cc3bb4
feat(voice): choose the call microphone and camera, run one call at a…
matheusfillipe Sep 8, 2026
196d39e
feat(desktop): record a chosen rectangle of the screen natively
matheusfillipe Sep 18, 2026
065c245
feat(composer): attach files to the message instead of a send dialog
matheusfillipe Sep 18, 2026
1d06d10
style(composer): slim the message bar and line it up with the user panel
matheusfillipe Sep 26, 2026
e998918
feat(chat): mark messages that name you or reply to you
matheusfillipe Sep 18, 2026
c110ba5
feat(sidebar): channel folders, reworked sidebar, menus and focus han…
matheusfillipe Sep 27, 2026
65ff951
feat(bots): redesign the workflow card, history list and shared menus
matheusfillipe Oct 1, 2026
ea84c24
feat(chat): decode and restyle server link previews
matheusfillipe Oct 2, 2026
9a40eec
feat(chat): fold long messages by input device, escape code block fal…
matheusfillipe Oct 3, 2026
67637a1
feat(composer): one suggestion list for members, emojis and slash com…
matheusfillipe Oct 3, 2026
bf1c246
feat(sidebar): pick the channel kind from the first prefix typed
matheusfillipe Oct 6, 2026
34ecb61
feat(notices): show the motd in server notices and on the welcome page
matheusfillipe Oct 6, 2026
a7471c3
feat(chat): compact links with their address in a hover card
matheusfillipe Oct 6, 2026
6072fb6
feat(bots): one workflow line per bot run that opens its card
matheusfillipe Oct 7, 2026
869fe82
feat(sidebar): user panel with a status menu, taller message bar
matheusfillipe Oct 7, 2026
6736160
chore(tauri): update to 2.12, point dev hot reload at the device host
matheusfillipe Oct 7, 2026
c0715d2
feat(sidebar): one in-place row for add channel and message someone
matheusfillipe Oct 7, 2026
0b694cf
feat(mobile): android pip, ios keyboard and foreground events, backgr…
matheusfillipe Oct 7, 2026
979fa30
chore(i18n): translate mobile, voice, media and settings strings
matheusfillipe Oct 7, 2026
9b00f48
fix(voice): rejoin on drops, keep mute, listen-only without a mic
matheusfillipe Oct 7, 2026
ac566d9
fix(media): working uploads, camera mic only while recording, voice m…
matheusfillipe Oct 7, 2026
bdd4032
fix(connection): no toast for a dropped live socket, never reopen cal…
matheusfillipe Oct 7, 2026
65dba82
fix(chat): whisper headers, verb menu labels, readable topic hint
matheusfillipe Oct 7, 2026
6427d48
fix(mobile): keyboard keeps header and input in view, safe areas on s…
matheusfillipe Oct 7, 2026
d158499
style: real theme colors, one primary button style
matheusfillipe Oct 7, 2026
12cca00
chore(i18n): translate call layout, join refusals and pm menu
matheusfillipe Oct 8, 2026
78897e0
fix(voice): stay in the channel on hang up, retry throttled joins
matheusfillipe Oct 8, 2026
e84bf44
feat(mobile): one control layer over phone calls, fullscreen, fold vi…
matheusfillipe Oct 8, 2026
7ef1a59
feat(chat): pms work like channels and every chat can be reordered
matheusfillipe Oct 8, 2026
44d0f6b
fix(mobile): no gap under the app, keyboard steps aside for sheets
matheusfillipe Oct 8, 2026
8993adc
ci: wait for the tauri 2.12 options file on ios, install only platfor…
matheusfillipe Oct 10, 2026
a9ae2b5
chore(i18n): translate camera, server notices and toasts
matheusfillipe Oct 9, 2026
33d8544
feat(notices): server errors go to server notices with a badge
matheusfillipe Oct 9, 2026
a4100c6
feat(media): youtube plays in the installed apps through the web origin
matheusfillipe Oct 9, 2026
53b0b7a
feat(settings): dialogs become pages, profile pane, autosave, one emb…
matheusfillipe Oct 9, 2026
581541e
test(voice): give the mocked store a ui state so leaving a call reads it
matheusfillipe Oct 15, 2026
3f4ae0a
fix(notices): keep server notices silent and mark plain notices with …
matheusfillipe Oct 16, 2026
e450431
fix(user-panel): show our nick on the selected server
matheusfillipe Oct 16, 2026
e6e329d
fix(irc): send NICK after SASL so a persistent session gets its nick …
matheusfillipe Oct 16, 2026
5f6ab67
fix(chat): mount a newly opened chat's message list on the first frame
matheusfillipe Oct 16, 2026
a6db13b
feat(history): keep read history on the device, fill offline gaps fro…
matheusfillipe Oct 9, 2026
3086247
fix(discover): cards fit the column width
matheusfillipe Oct 9, 2026
0b18188
refactor(history): one chathistory request helper, bouncer disconnect…
matheusfillipe Oct 9, 2026
cf57514
feat(search): search saved messages and jump to any message
matheusfillipe Oct 9, 2026
6f60805
fix(search): fetch missed messages for gaps while searching, mark a g…
matheusfillipe Oct 10, 2026
9ea090f
test(history): build the full older database layout in upgrade tests
matheusfillipe Oct 10, 2026
45b96af
fix(desktop): mac freeze from duplicate media probes, mobile swipe ba…
matheusfillipe Oct 10, 2026
13fe2ce
feat(discover): plugins page mockup
matheusfillipe Oct 10, 2026
6884907
style: frosted glass for menus, dialogs, sheets and the palette
matheusfillipe Oct 10, 2026
ca7d71d
feat(mobile): phone pages stack with swipe back, outlined search result
matheusfillipe Oct 10, 2026
93c7cc7
fix: share location map, chat stays at the bottom around the keyboard…
matheusfillipe Oct 10, 2026
999c96c
fix(mobile): drag the location map on touch, still page on ios keyboa…
matheusfillipe Oct 10, 2026
14291cd
fix(web): reload into the nickname prompt after disconnecting in sing…
matheusfillipe Oct 10, 2026
f42d80c
fix(tauri): try ipv4 first and give each address its own connect timeout
matheusfillipe Oct 11, 2026
1e5bd85
feat: suggest nicknames in the invite dialog
matheusfillipe Oct 11, 2026
d2a5434
fix(history): show saved private messages on servers that keep only c…
matheusfillipe Oct 11, 2026
648c40e
fix: show and select a server as soon as it is added
matheusfillipe Oct 11, 2026
a2be87a
fix(mobile): enable send with only an attachment
matheusfillipe Oct 11, 2026
61a89da
fix(history): drop saved messages that were redacted while we were away
matheusfillipe Oct 11, 2026
16c47df
fix: keep the reply when sending files
matheusfillipe Oct 11, 2026
aa5ce98
fix(desktop): ask for screen recording before picking a region, open …
matheusfillipe Oct 11, 2026
f85a9c2
fix(metadata): set our own metadata again after the app restarts
matheusfillipe Oct 11, 2026
4128514
fix: leave sentence punctuation out of links
matheusfillipe Oct 11, 2026
77af7ef
fix(metadata): follow our metadata when another of our clients change…
matheusfillipe Oct 11, 2026
578eaef
docs: never send long-lived credentials anywhere but the ircd
matheusfillipe Oct 11, 2026
ebd9f6a
fix(metadata): read our own metadata from the server only
matheusfillipe Oct 11, 2026
daed194
fix: fill members from WHO with metadata we already fetched
matheusfillipe Oct 11, 2026
a7f58e2
fix: stop firefox recordings that have no audio track
matheusfillipe Oct 11, 2026
23102a7
refactor(notices): drop the unused server notices link from toasts
matheusfillipe Oct 16, 2026
235c4b8
feat: use wide screens on settings and list pages
matheusfillipe Oct 11, 2026
99a3e6e
feat(chat): infinite scroll with skeletons, one bottom arrow and a ne…
matheusfillipe Oct 12, 2026
bcf416e
feat(read-marker): mark only what the user saw and clear unread acros…
matheusfillipe Oct 12, 2026
cf37a25
fix(irc): record our account from RPL_LOGGEDIN and RPL_LOGGEDOUT
matheusfillipe Oct 12, 2026
6061bfc
fix(connection): ping on wake and reconnect a socket that went dead
matheusfillipe Oct 12, 2026
d2ba662
feat(history): save older channel pages to disk and show them from there
matheusfillipe Oct 12, 2026
df977e8
fix(links): ask before opening links outside a link wrapper, includin…
matheusfillipe Oct 12, 2026
4858879
feat(history): open chats at the read marker with CHATHISTORY AROUND …
matheusfillipe Oct 12, 2026
7ed3bc5
feat(read-marker): derive unread from the read marker and follow new …
matheusfillipe Oct 12, 2026
70a76af
fix(chat): follow new messages while away until the new messages line…
matheusfillipe Oct 12, 2026
2c08b49
fix(bot-tools): finish workflows whose final answer arrives as a mult…
matheusfillipe Oct 12, 2026
7944bef
fix(chat): jump to the bottom when sending while scrolled up
matheusfillipe Oct 12, 2026
fdb07f7
fix(history): keep replayed history out of live state and confirm del…
matheusfillipe Oct 12, 2026
e4746ab
fix(focus): count clicks and keys as the app being in front and widen…
matheusfillipe Oct 13, 2026
e837627
fix(dm): show our own uploaded file once in private messages
matheusfillipe Oct 13, 2026
f6864ba
fix(irc): close the old socket when a server reconnects
matheusfillipe Oct 13, 2026
8a79d1a
fix(away): turn our own away dot green in channels when we come back
matheusfillipe Oct 13, 2026
2eda6e4
feat(servers): keep a second account on the same network as its own s…
matheusfillipe Oct 13, 2026
f09543f
fix(settings): send only what the user changes in channel, server and…
matheusfillipe Oct 13, 2026
18fed06
feat(discover): show network user and channel counts from the server …
matheusfillipe Oct 13, 2026
7fc2613
feat(chat): stack uploads, bot workflows and audio in one dock above …
matheusfillipe Oct 13, 2026
717803c
test(app): give the store mock a setState for the focus listeners
matheusfillipe Oct 13, 2026
e9214ea
fix(join): prefix bare names with the server's channel type and drop …
matheusfillipe Oct 13, 2026
d473b14
fix(chat): move upload progress to the top strip and bring back the o…
matheusfillipe Oct 13, 2026
57faffb
fix(chat): show real upload progress bars without spinners and size t…
matheusfillipe Oct 13, 2026
a435d63
fix(join): keep saved channels when an autojoin is refused and match …
matheusfillipe Oct 13, 2026
ec1b2dd
fix(links): open external links from the app itself on macOS
matheusfillipe Oct 14, 2026
e95e6de
docs(agents): add real server behaviour, platform, testing and PR rul…
matheusfillipe Oct 14, 2026
b0b2779
chore(docs): lint only tracked markdown files
matheusfillipe Oct 14, 2026
7d53b41
fix(history): stamp one receive time per line so saved history replay…
matheusfillipe Oct 14, 2026
0b130b8
docs(agents): add the CI gate, channel prefix and missing field rules
matheusfillipe Oct 14, 2026
9f3b5f6
fix(channels): detect channels from the server's CHANTYPES everywhere
matheusfillipe Oct 14, 2026
57a8b0e
fix(read-marker): mark a chat read when the user sends to it and keep…
matheusfillipe Oct 14, 2026
5f20591
fix(ios): vendor swift-rs with the Xcode 27 fix so iOS release builds…
matheusfillipe Oct 15, 2026
370ede8
fix(chat): fill a loading chat with skeleton rows and load older rows…
matheusfillipe Oct 16, 2026
6ad53dc
fix(chat): page past ignored users, ask for the read marker page once…
matheusfillipe Oct 16, 2026
bd4fc5b
fix(history): page past typing notices to the start of history and ma…
matheusfillipe Oct 16, 2026
6d63f9b
fix(chat): place the unread line once late history lands and keep the…
matheusfillipe Oct 16, 2026
a1da2eb
fix(chat): open a chat whose history never arrives and drop stale his…
matheusfillipe Oct 16, 2026
91157d8
fix(read-marker): send MARKREAD only while logged in to an account
matheusfillipe Oct 16, 2026
660c4d1
fix(chat): keep the visible rows in place when the top skeleton goes
matheusfillipe Oct 16, 2026
f610369
feat(notifications): show native notifications with tap routing and k…
matheusfillipe Oct 15, 2026
7c90fe0
feat(notifications): check for messages in the background on Android …
matheusfillipe Oct 15, 2026
a5f0dac
fix(notifications): route taps on iOS, read all history since the las…
matheusfillipe Oct 15, 2026
9a07fb1
fix(notifications): show the Obby crystal as the Android notification…
matheusfillipe Oct 15, 2026
23fef0b
feat(notifications): ask for notifications once when the app first opens
matheusfillipe Oct 15, 2026
056f70c
fix(persistence): ask for the persistence state once registration is …
matheusfillipe Oct 15, 2026
589075f
refactor(notifications): isolate the history poll as the fallback for…
matheusfillipe Oct 15, 2026
e55f0bb
fix(notifications): keep the poll through offline launches and only o…
matheusfillipe Oct 15, 2026
9a0af07
fix(notifications): poll web push servers too since the app webviews …
matheusfillipe Oct 15, 2026
5d5c3c0
fix(notifications): post android notifications through our plugin so …
matheusfillipe Oct 16, 2026
2eed31f
fix(notifications): show ios banners for notifications posted while t…
matheusfillipe Oct 16, 2026
0baf871
fix(notifications): land a tapped notification on its message instead…
matheusfillipe Oct 16, 2026
1e4591b
feat(webpush): soju.im/webpush client support
ValwareIRC May 20, 2026
4016c81
fix(notifications): configure the history poll once per change and sk…
matheusfillipe Oct 16, 2026
0a2446d
feat(webpush): add Enable Notifications toggle that drives push regis…
ValwareIRC May 20, 2026
b16b9ee
fix(notifications): jump to a tapped message once the page is visible
matheusfillipe Oct 16, 2026
a5900cd
feat(webpush): show channel + network in push notifications
ValwareIRC May 21, 2026
b7bdacd
fix(notifications): keep notifications on when iOS answers a prompt l…
matheusfillipe Oct 16, 2026
4a06ee3
feat(webpush): drive push from the notifications switch and share tit…
matheusfillipe Oct 15, 2026
8484016
fix(notifications): stop replaying old android taps and bound the ios…
matheusfillipe Oct 16, 2026
bfeb291
fix(webpush): route a push tap to the message time the server sent
matheusfillipe Oct 15, 2026
66ab5d1
fix(persistence): ask about persistence only while logged in to an ac…
matheusfillipe Oct 16, 2026
f186844
fix(webpush): give each server its own push registration so taps, key…
matheusfillipe Oct 16, 2026
edbc262
fix(notifications): alert once for a message android already notified
matheusfillipe Oct 16, 2026
22483d1
feat(notifications): show in settings how each server's notifications…
matheusfillipe Oct 16, 2026
7af3774
fix(webpush): subscribe as soon as the user allows notifications
matheusfillipe Oct 16, 2026
4c18cc8
feat(android): deliver notifications while closed through UnifiedPush…
matheusfillipe Oct 16, 2026
0f6226f
fix(webpush): drop stale push registrations and keep endpoints that c…
matheusfillipe Oct 16, 2026
d174b84
fix(webpush): filter channel pushes like live notifications and show …
matheusfillipe Oct 16, 2026
3380625
fix(voice): show voice channels with a mic icon and their bare name
matheusfillipe Oct 17, 2026
33a6b91
chore: merge stack/calls into stack/media
matheusfillipe Oct 17, 2026
434d4e4
chore: merge stack/media into stack/sidebar
matheusfillipe Oct 17, 2026
9c522ca
chore: merge stack/sidebar into stack/mobile
matheusfillipe Oct 17, 2026
f357885
chore: merge stack/mobile into stack/pages
matheusfillipe Oct 17, 2026
6639361
chore: merge stack/pages into stack/search
matheusfillipe Oct 17, 2026
6763884
chore: merge stack/search into stack/polish
matheusfillipe Oct 17, 2026
e023de8
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 17, 2026
b95fda6
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 17, 2026
8155ee0
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 17, 2026
5b1a932
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 17, 2026
8138968
fix(ui): bots in the side pane, member menu on any click, a gap-centr…
matheusfillipe Oct 17, 2026
87fc8d4
fix(metadata): keep our whole profile when one key changes and show a…
matheusfillipe Oct 17, 2026
f03814e
fix(notifications): stay quiet only for the chat in front of you and …
matheusfillipe Oct 17, 2026
9295067
fix(mobile): close the keyboard for message actions and swipe side pa…
matheusfillipe Oct 17, 2026
2253503
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 17, 2026
11de45f
fix(mobile): swipe a bottom sheet down to close it
matheusfillipe Oct 17, 2026
dd7c774
fix(chat): fit the completion list above the composer on phones with …
matheusfillipe Oct 17, 2026
098d756
fix(mobile): swipe down to close every bottom sheet, menus and dialog…
matheusfillipe Oct 17, 2026
75adc48
fix(media): stop probing a host after it blocks both requests
matheusfillipe Oct 17, 2026
03bc93a
fix(notifications): name the muted bell when it is what turns notific…
matheusfillipe Oct 19, 2026
1c7b578
fix(notifications): show the closed-app status only on mobile and ref…
matheusfillipe Oct 19, 2026
6266fcc
fix(notifications): pick the notifications source from one control an…
matheusfillipe Oct 19, 2026
28e95ec
fix(notifications): keep every notifications source choosable
matheusfillipe Oct 19, 2026
fdb66a0
fix(notifications): jump to the tapped message, fetching its page fro…
matheusfillipe Oct 19, 2026
1ba78b1
fix(read-marker): store a start marker for channels the server has no…
matheusfillipe Oct 19, 2026
335793b
chore(i18n): translate the notification settings strings
matheusfillipe Oct 19, 2026
a929119
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 19, 2026
209a9d0
fix(read-marker): send the start marker only for logged-in users
matheusfillipe Oct 19, 2026
b7fef1c
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 19, 2026
bb6cc42
fix(welcome): scroll a long motd, give it more width and hide the emp…
matheusfillipe Oct 19, 2026
8f5317e
fix(sidebar): tighten the gap above channel folders
matheusfillipe Oct 19, 2026
c1e61db
chore: merge stack/sidebar into stack/mobile
matheusfillipe Oct 19, 2026
4fc6a6a
chore: merge stack/mobile into stack/pages
matheusfillipe Oct 19, 2026
073311d
chore: merge stack/pages into stack/search
matheusfillipe Oct 19, 2026
f7b5f5c
chore: merge stack/search into stack/polish
matheusfillipe Oct 19, 2026
de12f85
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 19, 2026
a779966
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 19, 2026
b9c8492
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 19, 2026
190dab0
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 19, 2026
300247e
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 19, 2026
fc7de31
fix(chat): keep the read all chip compact with a full size tap area
matheusfillipe Oct 19, 2026
763acf0
fix(media): close an open image with the back button on android and i…
matheusfillipe Oct 19, 2026
ecee9c5
chore: merge stack/sidebar into stack/mobile
matheusfillipe Oct 19, 2026
d9cbbf5
chore: merge stack/mobile into stack/pages
matheusfillipe Oct 19, 2026
9398bee
chore: merge stack/pages into stack/search
matheusfillipe Oct 19, 2026
9afac2a
chore: merge stack/search into stack/polish
matheusfillipe Oct 19, 2026
85b4d19
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 19, 2026
c68a344
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 19, 2026
076121a
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 19, 2026
86fb3bd
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 19, 2026
737063c
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 19, 2026
809616a
feat(mobile): close sheets with the browser back gesture and ask befo…
matheusfillipe Oct 19, 2026
e713375
chore: merge stack/pages into stack/search
matheusfillipe Oct 19, 2026
de576c0
chore: merge stack/search into stack/polish
matheusfillipe Oct 19, 2026
e06cab6
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 19, 2026
2852903
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 19, 2026
0339d98
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 19, 2026
a96fd68
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 19, 2026
648ee10
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 19, 2026
54837b9
chore: merge main into stack/fixes
matheusfillipe Oct 20, 2026
5389d55
chore: merge stack/fixes into stack/calls
matheusfillipe Oct 20, 2026
b81bbc6
chore: merge stack/calls into stack/media
matheusfillipe Oct 20, 2026
46df832
chore: merge stack/media into stack/sidebar
matheusfillipe Oct 20, 2026
6ea1a6c
chore: merge stack/sidebar into stack/mobile
matheusfillipe Oct 20, 2026
9140928
chore: merge stack/mobile into stack/pages
matheusfillipe Oct 20, 2026
a6cbfcf
chore: merge stack/pages into stack/search
matheusfillipe Oct 20, 2026
3c8b2d3
chore: merge stack/search into stack/polish
matheusfillipe Oct 20, 2026
9a665f5
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 20, 2026
6eb837d
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 20, 2026
527dfd0
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 20, 2026
513bdc1
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 20, 2026
3c001d9
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 20, 2026
9295c7b
chore: merge main into stack/fixes
matheusfillipe Oct 20, 2026
3fe18f4
chore: merge stack/fixes into stack/calls
matheusfillipe Oct 20, 2026
00c6910
chore: merge stack/calls into stack/media
matheusfillipe Oct 20, 2026
b74b180
chore: merge stack/media into stack/sidebar
matheusfillipe Oct 20, 2026
cf73e2e
chore: merge stack/sidebar into stack/mobile
matheusfillipe Oct 20, 2026
07d935d
chore: merge stack/mobile into stack/pages
matheusfillipe Oct 20, 2026
3814edb
chore: merge stack/pages into stack/search
matheusfillipe Oct 20, 2026
d00fb7d
chore: merge stack/search into stack/polish
matheusfillipe Oct 20, 2026
9371f2f
chore: merge stack/polish into stack/wide-pages
matheusfillipe Oct 20, 2026
62ce1d1
chore: merge stack/wide-pages into stack/infinite-scroll
matheusfillipe Oct 20, 2026
d7ead7a
chore: merge stack/infinite-scroll into stack/notifications
matheusfillipe Oct 20, 2026
c2af813
chore: merge stack/notifications into feature/webpush
matheusfillipe Oct 20, 2026
b0cc70a
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 20, 2026
a7afa7d
fix(webpush): register after the server welcome, recover a stuck subs…
matheusfillipe Oct 21, 2026
cfc9688
chore: merge feature/webpush into stack/qa-round
matheusfillipe Oct 21, 2026
c73b9be
chore: merge main into stack/qa-round
matheusfillipe Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/components/chat/completionProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,18 +296,27 @@ export function slashProvider(
const signature = formatOptions(cmd.options);
const source = sourceLabel(cmd.source);
return (
<span className="flex-1 min-w-0 flex flex-col gap-0.5">
<span className="flex items-baseline gap-2 min-w-0">
<span className="truncate">/{cmd.name}</span>
<span className="flex-1 min-w-0 flex flex-col gap-0.5 [[data-compact]_&]:flex-row [[data-compact]_&]:items-baseline [[data-compact]_&]:gap-2">
<span className="flex items-baseline gap-2 min-w-0 [[data-compact]_&]:contents">
<span className="truncate [[data-compact]_&]:shrink-0 [[data-compact]_&]:font-semibold">
/{cmd.name}
</span>
{signature && (
<span className="row-meta truncate">{signature}</span>
<span className="row-meta truncate [[data-compact]_&]:shrink-0">
{signature}
</span>
)}
<span className="row-meta ml-auto shrink-0" title={source.title}>
<span
className="row-meta ml-auto shrink-0 [[data-compact]_&]:order-last"
title={source.title}
>
{source.label}
</span>
</span>
{cmd.description && (
<span className="row-meta truncate">{cmd.description}</span>
<span className="row-meta truncate [[data-compact]_&]:flex-1 [[data-compact]_&]:min-w-0">
{cmd.description}
</span>
)}
</span>
);
Expand Down
13 changes: 12 additions & 1 deletion src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import TitleTooltipLayer from "../ui/TitleTooltipLayer";
import { VoicePipOverlay } from "../voice/VoicePipOverlay";
import { ChannelList } from "./ChannelList";
import { ChatArea } from "./ChatArea";
import { MemberList } from "./MemberList";
import { PageLayer } from "./PageLayer";
import { ResizableSidebar } from "./ResizableSidebar";
import { ServerList } from "./ServerList";
import { SidePane } from "./SidePane";
import SwipeBackScreen from "./SwipeBackScreen";
import { VoiceChannelView } from "./VoiceChannelView";

const PAGE_ORDER: layoutColumn[] = ["serverList", "chatView", "memberList"];
Expand Down Expand Up @@ -312,7 +314,16 @@ export const AppLayout: React.FC = () => {
if (isNarrowView) {
return (
<div className="w-full h-full bg-discord-dark-100">
<SidePane />
{sidePane?.kind === "profile" || sidePane?.kind === "bot" ? (
<SwipeBackScreen
under={<MemberList />}
onBack={() => useStore.getState().closeProfile()}
>
<SidePane />
</SwipeBackScreen>
) : (
<SidePane />
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { t } from "@lingui/core/macro";
import { Trans, useLingui } from "@lingui/react/macro";
import type React from "react";
import { useEffect, useMemo, useState } from "react";
import { FaCircle } from "react-icons/fa";
import { useMemo } from "react";
import { FaChevronLeft, FaCircle, FaTimes } from "react-icons/fa";
import ircClient from "../../lib/ircClient";
import { confirmDestructive } from "../../lib/modal/ConfirmDialog";
import useStore from "../../store";
import type { BotPane as BotPaneTarget } from "../../store/types";
import type { BotCommand, PushBotInfo } from "../../types";
import LoadingSpinner from "../ui/LoadingSpinner";
import { type PageSection, PageShell } from "../ui/page/PageShell";
import type { PageProps } from "./PageHost";

type FilterMode = "all" | "server" | "channel";

const STATUS_CLS: Record<PushBotInfo["status"], string> = {
active: "bg-emerald-700/40 text-emerald-300 border border-emerald-600/50",
Expand Down Expand Up @@ -59,15 +56,6 @@ function useScopeBadge(): Record<
};
}

function useFilterLabels(): Record<FilterMode, string> {
const { t } = useLingui();
return {
all: t`All`,
server: t`Server-wide`,
channel: t`Channel`,
};
}

interface BotDetailProps {
bot: PushBotInfo;
isOper: boolean;
Expand Down Expand Up @@ -271,155 +259,112 @@ function isBotReachable(
return !!bot.channels?.some((ch) => joinedChannels.has(ch.toLowerCase()));
}

const BotsPage: React.FC<PageProps<"bots">> = ({ page }) => {
const { serverId, botNick } = page;
export const BotPane: React.FC<{ target: BotPaneTarget }> = ({ target }) => {
const { serverId, botNick } = target;
const server = useStore((s) => s.servers.find((srv) => srv.id === serverId));
const currentUser = useStore((s) => s.currentUser);
const closePage = useStore((s) => s.closePage);
const isNarrowView = useStore((s) => s.ui.isNarrowView);
const closeProfile = useStore((s) => s.closeProfile);
const openBot = useStore((s) => s.openBot);
const requestBotCommandPick = useStore((s) => s.requestBotCommandPick);
const FILTER_LABELS = useFilterLabels();
const [filter, setFilter] = useState<FilterMode>("all");
const [query, setQuery] = useState("");
const [selectedNick, setSelectedNick] = useState<string | null>(
botNick ?? null,
);
useEffect(() => {
if (botNick) setSelectedNick(botNick);
}, [botNick]);

const isOper = (() => {
const myNick = currentUser?.username;
if (!myNick || !server) return false;
const me = server.users.find((u) => u.username === myNick);
return !!me?.isIrcOp;
})();
const isOper = !!server?.users.find(
(u) => u.username === currentUser?.username,
)?.isIrcOp;

const bots: PushBotInfo[] = useMemo(() => {
if (!server?.bots) return [];
// Reachability filter: server-scope bots are always shown
// (reachable from any context). Channel-scope bots only appear
// when the local user still shares a channel with them -- this
// matches the server-side discovery contract (server-scope on
// burst, channel-scope on LOCAL_JOIN of a shared channel) and
// hides stale entries left over after the user parts a channel.
const joinedChannels = new Set(
(server.channels ?? []).map((c) => c.name.toLowerCase()),
(server?.channels ?? []).map((c) => c.name.toLowerCase()),
);
return Object.values(server.bots)
return Object.values(server?.bots ?? {})
.filter((b) => isBotReachable(b, isOper, joinedChannels))
.filter((b) => filter === "all" || b.scope === filter)
.filter((b) =>
query
? b.nick.toLowerCase().includes(query.toLowerCase()) ||
(b.realname ?? "").toLowerCase().includes(query.toLowerCase())
: true,
)
.sort((a, b) => {
const sa = a.status === "active" ? 0 : a.status === "pending" ? 1 : 2;
const sb = b.status === "active" ? 0 : b.status === "pending" ? 1 : 2;
if (sa !== sb) return sa - sb;
return a.nick.localeCompare(b.nick);
});
}, [server?.bots, server?.channels, filter, query, isOper]);
.sort((a, b) => a.nick.localeCompare(b.nick));
}, [server?.bots, server?.channels, isOper]);

const selected = selectedNick
? server?.bots?.[selectedNick.toLowerCase()]
: undefined;
const selected = botNick ? server?.bots?.[botNick.toLowerCase()] : undefined;

const send = (subcmd: string) => {
if (!isOper || !selected) return;
ircClient.sendRaw(serverId, `PUSHBOT ${subcmd} ${selected.nick}`);
if (subcmd === "DELETE") setSelectedNick(null);
if (subcmd === "DELETE") openBot(serverId);
};

const sections: PageSection[] = bots.map((b) => ({
id: b.nick,
label: b.nick,
icon: server?.botCommandsLoading?.includes(b.nick.toLowerCase()) ? (
<LoadingSpinner size="sm" text="" />
) : (
<FaCircle
className={`text-[8px] ${b.online ? "text-emerald-400" : "text-discord-text-muted/60"}`}
/>
),
description: b.realname || undefined,
}));

const controls = (
<div className="flex flex-col gap-2">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={t`Search bots`}
aria-label={t`Search bots`}
className="field-input"
/>
<div className="flex gap-1 text-xs">
{(["all", "server", "channel"] as FilterMode[]).map((f) => (
return (
<div className="flex flex-col h-full min-h-0 overflow-x-hidden bg-discord-dark-100">
<header className="h-12 shrink-0 flex items-center gap-1.5 px-2 border-b border-white/[0.06]">
{(isNarrowView || selected) && (
<button
type="button"
key={f}
aria-pressed={filter === f}
onClick={() => setFilter(f)}
className={`px-2 py-1 rounded transition-colors ${
filter === f
? "bg-discord-primary text-white"
: "bg-discord-dark-400 text-discord-text-muted hover:text-white"
}`}
onClick={selected ? () => openBot(serverId) : closeProfile}
className="icon-button w-10 h-10 rounded-lg"
title={t`Back`}
aria-label={t`Back`}
>
{FILTER_LABELS[f]}
<FaChevronLeft className="w-3.5 h-3.5" />
</button>
))}
<div className="ml-auto text-discord-text-muted self-center">
{bots.length}
</div>
)}
<h2 className="flex-1 min-w-0 truncate px-1 text-[15px] font-semibold text-white">
<Trans>Bots</Trans>
</h2>
{!isNarrowView && (
<button
type="button"
onClick={closeProfile}
className="icon-button w-10 h-10 rounded-lg"
title={t`Close`}
aria-label={t`Close`}
>
<FaTimes className="w-3.5 h-3.5" />
</button>
)}
</header>
<div className="flex-1 min-h-0 overflow-y-auto overscroll-contain px-3 py-4">
{selected ? (
<BotDetail
bot={selected}
isOper={isOper}
loading={
!!server?.botCommandsLoading?.includes(
selected.nick.toLowerCase(),
)
}
onAction={send}
onPickCommand={(cmd) => {
requestBotCommandPick(serverId, selected.nick, cmd);
closeProfile();
}}
/>
) : bots.length === 0 ? (
<div className="py-8 text-center text-sm text-discord-text-muted">
<Trans>No bots registered on this network yet.</Trans>
</div>
) : (
<ul className="flex flex-col gap-1">
{bots.map((b) => (
<li key={b.nick}>
<button
type="button"
onClick={() => openBot(serverId, b.nick)}
className="w-full text-left flex items-center gap-2 rounded px-3 py-2 text-discord-text-normal hover:bg-discord-dark-400 transition-colors"
>
{server?.botCommandsLoading?.includes(
b.nick.toLowerCase(),
) ? (
<LoadingSpinner size="sm" text="" />
) : (
<FaCircle
className={`text-[8px] ${b.online ? "text-emerald-400" : "text-discord-text-muted/60"}`}
/>
)}
<span className="truncate">{b.nick}</span>
</button>
</li>
))}
</ul>
)}
</div>
</div>
);

const hasAnyBot = Object.keys(server?.bots ?? {}).length > 0;

const detail = selected ? (
<BotDetail
bot={selected}
isOper={isOper}
loading={
!!server?.botCommandsLoading?.includes(selected.nick.toLowerCase())
}
onAction={send}
onPickCommand={(cmd) => {
requestBotCommandPick(serverId, selected.nick, cmd);
closePage();
}}
/>
) : (
<div className="py-12 text-center text-sm text-discord-text-muted">
<Trans>Select a bot to see its commands and management actions.</Trans>
</div>
);

return (
<PageShell
title={t`Bots`}
sections={sections}
activeSection={selected?.nick}
onSectionChange={setSelectedNick}
startOnSection={!!botNick}
navExtra={controls}
>
{bots.length === 0 ? (
<div className="py-8 text-center text-sm text-discord-text-muted">
{hasAnyBot ? (
<Trans>No bots match. Clear the search or change the filter.</Trans>
) : (
<Trans>No bots registered on this network yet.</Trans>
)}
</div>
) : (
detail
)}
</PageShell>
);
};

export default BotsPage;
export default BotPane;
22 changes: 5 additions & 17 deletions src/components/layout/ChannelMessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,6 @@ export const ChannelMessageList = forwardRef<
}
}, [isScrolledUp]);

// Reset ref-tracked windowing state when switching channels.
// visibleMessageCount is NOT reset here — useState(initialScrollState?.visibleCount ?? DEFAULT_VISIBLE_MESSAGE_COUNT)
// already initializes it correctly on mount, and this effect runs once on mount for the
// same channelKey (each instance is bound to exactly one channel by the parent key={}).
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional full reset on channel change
useEffect(() => {
prevFilteredLengthRef.current = 0;
prevFirstMsgIdRef.current = null;
prevScrollHeightRef.current = 0;
pendingPrependRef.current = false;
resizeObserverPrevSHRef.current = 0;
}, [channelKey]);

// Whether we moved the scroll to put the anchored row back where it was.
const keepAnchoredRow = (): boolean => {
const container = messagesContainerRef.current;
Expand Down Expand Up @@ -1063,18 +1050,19 @@ export const ChannelMessageList = forwardRef<
return (
<Fragment key={message.id}>
{message.id === firstUnreadId && (
// We draw the line over the boundary with no height of its own, so the messages stay put when it comes and goes.
// We draw the line with no height of its own, so the messages stay put when it comes and goes.
// A row with a header has a 16px gap above it, and the line sits in the middle of it.
// Above the first row there is nothing to overlap, so there it takes its own height.
<div
data-unread-line
className={`relative z-[5] pointer-events-none ${originalIndex === 0 ? "h-5" : "h-0"}`}
>
<div
className={`absolute inset-x-4 -translate-y-1/2 flex items-center gap-2 text-xs font-semibold text-discord-red ${originalIndex === 0 ? "top-1/2" : "top-0"}`}
className={`absolute inset-x-4 -translate-y-1/2 flex items-center gap-2 text-xs font-semibold text-discord-red ${originalIndex === 0 ? "top-1/2" : showHeader ? "top-2" : "top-0"}`}
>
<span className="hairline flex-1 bg-discord-red/70" />
<span className="px-1 rounded bg-discord-dark-200">
<Trans>New messages</Trans>
<span className="px-1.5 h-4 leading-4 text-[10px] rounded-sm bg-discord-red text-white">
<Trans>New</Trans>
</span>
</div>
</div>
Expand Down
Loading
Loading