Commit Graph
63602 Commits
Author SHA1 Message Date
Régis Hanol 9d444e8d7a FIX: Return file paths from FileStore download methods (#37760)
`BaseStore#download` and `download!` previously returned open `File`
objects via `get_from_cache` (which called `File.open`). Nearly all
callers (14 of 16) only needed the `.path` and never closed the handle,
leaking file descriptors. Under load this can exhaust FDs and crash
with `Errno::EMFILE`.

This commit changes `download` and `download!` to return file path
strings instead of `File` objects. Since the cached file at
`tmp/download_cache/` persists on disk, callers that need to read
content can simply use `File.read(path)`.

The public API is simplified to two methods:

- `download`: safe, rescues all errors and returns `nil` (absorbs
  the former `download_safe` behavior)
- `download!`: raises `DownloadError` on failure

A deprecated `download_safe` alias is kept for plugin compatibility.

The block/yield pattern is removed entirely since it's no longer needed.
There are no file handles to manage. All call sites are updated to
use the path directly, dropping `{ |f| f.path }` blocks. The two
callers that actually read file content are updated:

- `static_controller.rb` → `File.read(path)`
- `digest_rag_upload.rb` → `File.open(path)` (streams content)
2026-02-17 11:56:50 +01:00
Régis Hanol 17c414186f FIX: Include poll options in HTML email notifications (#37812)
Previously, the HTML variant of email notifications replaced the entire
poll with a "Click to view the poll" link, while the plain-text variant
included all the poll options. This meant the HTML email contained less
information than its plain-text counterpart.

Now the `reduce_cooked` callback preserves the poll title and options
list, strips the interactive parts (vote counts, buttons) and
`data-poll-option-id` attributes, and still appends the link to vote.

This works for all poll types (regular, multiple, number, ranked_choice)
since they all share the same `.poll-container > ul/ol > li` structure.

https://meta.discourse.org/t/393728
2026-02-17 11:39:42 +01:00
David Taylor 253ee0c55a DEV: Check RUN_PITCHFORK in bin/ember-cli and bin/unicorn (#37813)
Pitchfork is now the default, so everyone should be using it in
development (unless specifically overridden via RUN_PITCHFORK=0)
2026-02-17 10:21:18 +00:00
David Taylor 2d615863d2 DEV: Fix AnonymousCache for pitchfork in dev/test modes (#37852)
In development mode, AnonymousCache is disabled by default, so we hadn't
noticed this. And we haven't started using pitchfork in test mode yet.

Pitchfork adds the `Rack::Lint` middleware to the top of the stack in
non-production environments:
https://github.com/Shopify/pitchfork/blob/c95f7a6e/lib/pitchfork.rb#L100.
This causes a failure on our non-rack-spec-compliant call to
`env[Rack::RACK_INPUT].size`.

Pitchfork implements the `#size` method, but marks it as for
backward-compatibility for Rack < 1.2:
https://github.com/Shopify/pitchfork/blob/c95f7a6e/lib/pitchfork/tee_input.rb#L60-L71.
The implementation reads the entire string from the stream and checks
the length. In our case, we can just try reading one byte to determine
whether any body has been received. This is compliant with the modern
Rack spec, and will pass the `Rack::Lint` middleware checks.
2026-02-17 09:12:02 +00:00
Régis Hanol d2d1b4f53d FIX: use FinalDestination#get for RSS polling to fix auth and eliminate redundant requests (#37840)
RSS polling from one Discourse site to another was failing when using
API key authentication via query parameters (e.g., /c/category/123.rss
?api_key=XXX&api_username=YYY). This affected private or login_required
sites where RSS feeds require authentication.

The issue occurred because FinalDestination defaults to HEAD requests
for URL resolution. Discourse's authentication system only allows API key
authentication via query parameters for GET requests (defined in
PARAMETER_API_PATTERNS in Auth::DefaultCurrentUserProvider).

Additionally, both `fetch_raw_feed` and `set_image_as_thumbnail` used a
two-step approach: `FinalDestination#resolve` (HEAD) to follow
redirects, then a separate `Excon` GET to download the content — making two full
HTTP requests per URL.

Switch both methods to use `FinalDestination#get`, which handles
redirect following, SSRF protection, and body streaming in a single request. This
is the same approach used by `FileHelper` and `RetrieveTitle` elsewhere
in the codebase.

Ref - t/174086
2026-02-17 09:39:20 +01:00
Régis Hanol 32215213bc FEATURE: improve chat search result ranking with server-side match quality (#37031)
When searching for chatables (users, groups, channels), results that
exactly match the search term now appear before prefix matches, which
in turn appear before partial matches. Previously, match quality was
computed client-side based on the result names, but this was unreliable
for DM channels where the match depends on participant usernames that
aren't always directly visible.

The server now computes a match_quality score (exact=1, prefix=2,
partial=3) via SQL CASE expressions and returns it through the
serializer. For DM channels, MIN() is used across all participants
so the best match among them determines the channel's rank.

The client-side sorting was simplified to use the server-provided
scores, and the type priority was reordered to users > DM channels >
category channels > groups, which better matches how people typically
search in chat.

LIKE patterns are now escaped with sanitize_sql_like to prevent
wildcard characters in search terms from producing unexpected results.
2026-02-17 09:11:13 +01:00
Martin Brennan edb1875027 FIX: feature_stats leaks restricted-category topic metadata counts (#37875)
## Summary

`feature_stats` endpoint leaks topic metadata (pinned/banner counts)
from read-restricted categories to anonymous and unauthorized users.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/350
- Original Commit:
https://github.com/discourse/discourse/blob/main/app/controllers/topics_controller.rb

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-17 17:32:06 +10:00
Sam 24f9eff898 DEV: attempt to fix flakey specs (#37874)
lightbox related specs have been flaking due to incorrect assumptions
about
initialization.

This attempts to resolve it.
2026-02-17 16:50:33 +11:00
Sam 1fa926470b FIX: Missing visibility authorization in flag allows flagging comments in inaccessible topics (#37856)
Missing visibility authorization in
`PostVoting::CommentsController#flag` allows users to flag comments on
posts/topics they cannot access

This is annoying, but not just a bug.
2026-02-17 16:28:31 +11:00
Sam b570d9cd01 FIX: Stop-stream endpoint allows unauthorized bot-control action via visibility-only check (#37860)
The `stop_streaming_response` endpoint in `BotController` allowed any
authenticated user who could see a bot reply post to cancel its
streaming generation, even if they were not authorized bot users (not in
`ai_bot_allowed_groups` or `ai_bot_enabled` was false).

This is extremely minor, the PMs usually will not contain other users
unless invited anyway.
2026-02-17 16:19:02 +11:00
Sam bb4b5ecdb9 FIX: Non-listable and disabled badges exposed via XHR JSON requests (#37869)
Non-listable and disabled badges exposed to anonymous users via XHR JSON
requests to `GET /badges.json`.

This is minor badges are not a security feature, but this is nice
defense in depth
2026-02-17 16:17:01 +11:00
Sam 2f529a5899 FIX: Slow mode bypass via auto_track parameter (#37868)
Users can bypass "Slow Mode" topic restrictions by sending `auto_track:
false` in the JSON body when creating posts via `POST /posts.json`

Slow mode is not a security feature, but it is nice to seal this edge
case. Instead of leaning on topic user to get the information we now
look directly at posts by a user
2026-02-17 16:16:50 +11:00
Sam 580b7ba678 FIX: Unauthenticated author spoofing via discourse_username during embed import (#37867)
Unauthenticated author spoofing via `discourse_username` parameter in
the `/embed/comments` endpoint allows an anonymous attacker to create
embedded topics. This feature has been deprecated for years now (since
3.2) so this commit removes it.

Lean no meta tags instead.
2026-02-17 16:16:40 +11:00
Sam 558389b5f0 FIX: Allow artifacts on posts via API (#37870)
Previously we force artifacts on pms only, this allows them to be bound
to post in any category.

The PM restriction was over restrictive.
2026-02-17 16:16:21 +11:00
Martin Brennan b9fb6dca9e FIX: Cache rewind reports with user ID not username (#37872)
User ID is immutable so it's a better fit for a
cache key
2026-02-17 15:14:54 +10:00
Martin Brennan f1c600a8ab FIX: BookmarksBulkAction#delete passing integer to guardian instead of Bookmark object (#37871)
## Summary

Broken authorization check in `BookmarksBulkAction#delete` — passes
Integer to Guardian instead of Bookmark object, causing
`guardian.can_delete?` to always return `true` for any authenticated
user regardless of bookmark ownership.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/233
- Original Commit:
https://github.com/discourse/discourse/blob/main/app/controllers/bookmarks_controller.rb

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-17 15:14:46 +10:00
SamandMartin Brennan ede2ffbd86 FEATURE: redesign house ads admin UI with FormKit (#37799)
Modernize the house ads admin interface to align with
Discourse admin UI patterns:

- Extract HouseAdForm component using FormKit, replacing
  the controller-heavy approach with observers and buffered
  state
- Redesign index page with DPageSubheader, tabbed nav
  (Ads/Settings), d-admin-table, and empty state component
- Simplify show page to BackButton + HouseAdForm
- Harden backend controller: use Discourse::NotFound,
  derive id from URL params instead of body, remove id
  from permitted params to prevent injection, remove
  update-creates-if-missing behavior
- Add guard in route for missing house ads
- Fix group mapping to handle both object and scalar values
- Rewrite system spec as full lifecycle test with page object
- Use fab! over let in request specs, add coverage for 404
  and id injection edge cases

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
2026-02-17 13:37:17 +10:00
Sam fb103c2ac7 FIX: Empty-scopes bypass allows untrusted client registration and downstream scope/redirect policy bypass (#37855)
Empty-scopes bypass allows untrusted client registration via
`UserApiKeyClientsController#create` — sending `scopes=","` creates a
client with zero persisted scopes, bypassing downstream scope
restrictions.

This hardens the param to avoid this kind of empty scope registration.
2026-02-17 12:39:09 +11:00
Samandchapoi 97af5e2540 FEATURE: improve UX of API key generation (#37789)
<img width="1248" height="656" alt="image"
src="https://github.com/user-attachments/assets/fa482c37-0107-4382-aa28-1a5a39d0370a"
/>

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
2026-02-17 12:03:13 +11:00
Sam 911930ae42 FIX: Retry endpoint only checks visibility, not bot-usage authorization (#37851)
The `retry_response` endpoint in `BotController` only checked post
visibility (`guardian.ensure_can_see!`) but did not enforce
`ai_bot_enabled` or `ai_bot_allowed_groups` membership.

This defends this endpoint in depth. In reality nobody has access anyway
to these PMs and risk around regeneration is very low.
2026-02-17 12:02:49 +11:00
Martin Brennan 86c5bee7cb UX: New feature empty list message (#37835)
c.f.
https://meta.discourse.org/t/error-seeing-experimental-features-in-admin-whats-new/395891

The new feature feed incorrectly was showing that there
was an error loading the feed, just because Experimental
was clicked and no results were found.
2026-02-17 10:29:26 +10:00
Joffrey JAFFEUX c24102d365 DEV: inits our .skills directory (#37850)
This first commits adds the upcoming change directory, which allows you
to ask to your llm: "Help me add an upcoming change" and will get you
through the various questions/tasks required to do this.
2026-02-17 09:35:23 +11:00
Régis Hanol 55e992f507 FIX: add og:image:width and og:image:height to OpenGraph tags (#37849)
Facebook's crawler requires image dimension metadata to reliably display
image previews when Discourse links are shared. Currently, we only emit
`og:image` without the structured dimension properties, which causes
Facebook (and other consumers) to skip the image in share previews.

This adds `og:image:width` and `og:image:height` meta tags for topic
pages by exposing the image upload's dimensions through TopicView and
passing them to `crawlable_meta_data`. The dimensions are only emitted
when both width and height are available on the upload record.

https://meta.discourse.org/t/395484
2026-02-16 20:30:26 +01:00
Kris 6d0e36be32 DEV: switch to window.matchMedia when detecting narrowDesktopView (#37833)
in CSS we check `< 48rem` but in `narrow-desktop.js` we check
`document.body.getBoundingClientRect().width < 768`. At 100% zoom these
are the same.

At other zoom levels, REMs scale with zoom but
`document.body.getBoundingClientRect().width` falls out of sync and
compares to the static 768px — so we get a mismatch and in some cases
this causes unexpected styling issues

What we can do to sync these up is use `window.matchMedia("(min-width:
48rem)")` instead
2026-02-16 12:40:37 -05:00
Joffrey JAFFEUX 9ae4972e72 FIX: ensures only staff can check slugs (#37846)
This is not considered a security issue as enumeration on a string is
very time consuming and would only give this info: this slug exists.

The fix ensures the slug is only for logged in and staff, and adds
tests.
2026-02-16 18:23:46 +01:00
Régis Hanol 7a15bf5ac0 FIX: Standardize Unicode encoding for route params in construct_url_with (#37843)
When `unicode_usernames` is enabled and the default locale is
non-English, `Jobs::EnsureDbConsistency` renames automatic groups to
localized Unicode names (e.g., "admins" → "管理员"). Visiting the group's
assigned topics page then triggers a 500 error
(`URI::InvalidComponentError`) because the Unicode group name isn't
properly encoded when building pagination URLs.

The encoding of route params affected by `unicode_usernames` was
scattered across multiple places:
- `:username` was encoded in `page_params`
- `:group_name` was encoded in `construct_url_with`
- `:groupname` (assign plugin) was not encoded anywhere → 500 error

This consolidates all encoding into `construct_url_with`:
- `page_params` now passes raw values for all route params (`:username`,
`:group_name`, `:groupname`, `:period`) via a single loop
- `construct_url_with` encodes all three Unicode-sensitive params
(`:username`, `:group_name`, `:groupname`) in both `page_params` and
`opts` when `unicode_usernames` is enabled
- The unencode block now uses simple string splitting instead of
`URI.parse`, which cannot handle Unicode characters in paths

https://meta.discourse.org/t/396092
2026-02-16 18:08:05 +01:00
Régis Hanol d7a53ada16 FIX: support CJK and spaceless scripts in watched word boundaries (#37844)
Watched words failed to match in CJK (Chinese, Japanese, Korean) and
other spaceless scripts because word boundary detection relied on
whitespace or non-word characters. Languages like Chinese don't use
spaces between words, so "测试" inside "这是一个测试文本" was never matched.

Introduce a SPACELESS_SCRIPTS constant covering Han, Hiragana, Katakana,
Hangul, Thai, Lao, Myanmar, Khmer, and Tibetan Unicode ranges. Update
`match_word_regexp` for both Ruby and JS engines so that characters from
these scripts are treated as word boundaries. This allows a CJK watched
word to match when surrounded by other CJK characters, and a Latin
watched word to match when adjacent to CJK text (e.g., "Test" in
"我的Test很好"), while still preventing partial Latin matches (e.g.,
"Testing" does not match "Test").

Also fix the admin watched word testing modal to use `RegExp.exec()`
with capture group extraction instead of `String.match()`, since the new
boundary patterns include a leading consuming group.

Remove the outdated "non-chrome browsers do not support lookbehind"
comment — all major browsers have supported lookbehind since 2023.

https://meta.discourse.org/t/71288
https://meta.discourse.org/t/396109
2026-02-16 18:04:27 +01:00
chapoi ced45ec71e UX: minor tweaks to table columns (#37845)
https://meta.discourse.org/t/invisible-button-text-layout-misalignment-on-horizon-theme/396180

This area will be reworked more extensively soon, so this commit is just
a small bandaid to improve it in the meantime.
2026-02-16 17:21:02 +01:00
chapoi e51153185f UX: hide topic layout btn on Horizon (#37847)
This button is not hooked up in any way to the Horizon topic cards.
Hiding for now, until we repurpose for low-context/high-context
switching
2026-02-16 17:20:49 +01:00
Penar Musaraj e05b85ad70 FIX: Passkey login not outputting error messages with suspended user and screened IP checks (#37827)
This was a missing early check for passkey logins. Users wouldn't be
able to log in, even though the passkey login check would return true
because the current user provider would still return `nil`.

This does improve the UI though, previously we wouldn't show an error
message, now we will, via a modal (like in regular and email logins).
2026-02-16 09:28:03 -05:00
Joffrey JAFFEUX bd774e5913 FIX: ensures admin can't set system property on badges (#37820)
This was only doable by admins so not considered as security, but this
could cause errors if an admin was doing it inadvertently. This commit
ensures it's not possible anymore and adds tests for it.
2026-02-16 12:33:32 +01:00
Joffrey JAFFEUX 1267b818a3 PERF: extract shared DiskCacheEviction utility for disk caches (#37842)
- Extracts a shared `DiskCacheEviction.evict` utility used by both
avatar proxy cache (`tmp/avatar_proxy/`) and download cache
(`tmp/download_cache/`)
- `base_store.rb` no longer sorts the entire file list on every
`cache_file` call, eviction only runs when count exceeds the limit
- Fixes a concurrency bug in `proxy_avatar` where a file could be
evicted between `File.exist?` and `send_file`, now rescues
`Errno::ENOENT` / `ActionController::MissingFile` and falls back to
`render_blank`
2026-02-16 12:24:38 +01:00
chapoi f2fd5c36b9 UX: fix horizon card reply count (#37841)
https://meta.discourse.org/t/horizon-theme-incorrect-reply-count-display/383830
2026-02-16 12:16:05 +01:00
Régis Hanol 574ca5f825 FIX: Use localized auto group names instead of hardcoded English (#37757)
Automatic group names (everyone, staff, admins, etc.) are translated
based on the site's default locale. However, the `AUTO_GROUPS` JS
constant had hardcoded English names which were used directly in several
places — most notably during category creation. This caused the
"everyone" group to always display its English name in the permissions
table, even on sites with a different default locale.

This removes the `name` and `display_name` fields from `AUTO_GROUPS`,
keeping only `id` and `automatic`. A new `groupsById` getter on the
`Site` model provides a lookup map built from server-provided group data
(which contains the properly localized names). All consumers now resolve
group names via `site.groupsById[groupId].name` instead of reading from
the constant.

The avatar-flair system is also refactored from name-based to ID-based
lookups, and group identity checks in `user.js` now compare by `id`
rather than `name`.

https://meta.discourse.org/t/395225
2026-02-16 09:41:36 +01:00
Sam 4879ae0ae5 FIX: Respect enable_emoji_shortcuts in ProseMirror (#37837)
The ProseMirror emoji input rule was converting text shortcuts
like `:)` into emoji images regardless of the
`enable_emoji_shortcuts` site setting. This adds a guard to
skip the conversion when the setting is disabled.
2026-02-16 16:39:13 +11:00
72a51e8943 DEV: clean up splash SVG on upload, not when rendering (#37681)
This moves the splash screen SVG processing (SMIL and script stripping)
to upload rather than render for the `splash_screen_image` setting.

While testing I also found a case where SVG dimensions could make splash
images very tiny, so I strip out the dimensions (as long as a viewbox is
present) so the SVG can scale to fit the wrapper better.

---------

Co-authored-by: Régis Hanol <regis@hanol.fr>
Co-authored-by: Martin Brennan <martin@discourse.org>
2026-02-16 13:14:35 +10:00
Krzysztof Kotlarek 33d0292170 FIX: Exclude suspended users from suspect users review queue (#37796)
What is the problem?

The `approve_suspect_users` feature flags new accounts as suspect if
they have a bio/website but minimal reading activity. The
`Jobs::EnqueueSuspectUsers` scheduled job runs every 2 hours and targets
accounts that are at least 1 day old. If an admin suspends a spammy user
before that job runs, the suspended user still gets added to the review
queue, creating unnecessary noise for moderators reviewing an
already-handled case.

What is the solution?

Added the `User.not_suspended` scope to the query in
`Jobs::EnqueueSuspectUsers` so that suspended users are excluded. Users
who have already been suspended by an admin have been dealt with and
should not appear in the suspect users review queue.
2026-02-16 10:46:52 +08:00
Martin BrennanandRégis Hanol e768f0c3cd FEATURE: Chat channel list in category UI (#37750)
This commit adds a list of channels associated with
the category under a new Chat tab in the category UI.

This tab is only shown if `enable_simplified_category_creation`
is enabled, and the chat plugin is enabled. The tab also only
shows if the category has any associated channels.

A plugin API needs to be added (`registerEditCategoryTab`) to
allow the chat plugin to register the tab on the category page
with the associated component. We also allow the chat API channels
controller to accept a chatable_id to list the channels associated with
the category.

---------

Co-authored-by: Régis Hanol <regis@hanol.fr>
2026-02-16 10:41:22 +10:00
Martin Brennan e154a90427 FEATURE: Disallow selecting groups for some upcoming changes (#37801)
Some upcoming changes are all-or-nothing — they affect the whole site
or only admins, so enabling them per-group makes no sense. This adds a
`disallow_enabled_for_groups` flag to upcoming change metadata that
restricts the "Enabled for" dropdown to only "Everyone" and "No One",
and clears any existing group assignments server-side as a safety net.
2026-02-16 10:19:01 +10:00
Ruben Oussoren ba0a319dee FIX email deduplication incorrectly replacing valid user emails (#37824)
The process_user_email method was replacing valid emails with random
@email.invalid addresses because the deduplication check treated a
user's own email as a duplicate.

The @emails hash is populated during import_users, so when
import_user_emails runs later, the check !@emails.has_key?(email) fails
for the user's own email, triggering the random_email fallback.

Updated the condition to allow an email if it belongs to the same user:
(!@emails.has_key?(email) || @emails[email] == user_id)
2026-02-13 16:42:16 -05:00
Jordan Vidrine e24970d5e9 FIX: Change @class to @triggerClass on ip lookup menu (#37831) 2026-02-13 15:39:52 -06:00
Sam 46cde9cbad DEV: Improve rate limiting on hyde search (#37794)
Hyde search now limits at a total of 60 anon searches a min and per IP 

Previously it just put all anons in 1 bucket.
2026-02-13 17:00:00 -03:00
Jordan Vidrine 23a7730831 FIX: header-icons-fix (#37823) 2026-02-13 13:00:04 -06:00
Kris 749d8f4325 UX: reposition and restyle translation features to improve experience (#37821)
This adds some polish to our translation feature and involves changing
some button positions and styling.

* Updated to the Font Awesome 7 "language" icon — this is an out-of-band
update but feels worth it for the better icon (it's larger/clearer)
* Repositioned the translation toggle so it doesn't stack for admins
above the timeline
* Repositioned the mobile translation toggle so it matches the desktop
position (admin first, then translation button)
* Removed use of the globe icon for translations — everything now uses
the consistent "language" icon to make it clear that it's all part of
the same system
* Adjusted a couple issues where things weren't as reactive as they
could be (switching from composer directly to editing a translation
would cause a button to remain when it shouldn't)
* Styling improvement for "active" state of translations
* When toggling translations on/off we now confirm with a toast 
* Moved the language dropdown in the composer when editing translations,
this saves a good amount of space

Toasts appear on toggle: 
<img width="250" alt="image"
src="https://github.com/user-attachments/assets/536b84bd-8499-4f01-8ffa-5afa85b7f9e0"
/>



Before|After — the icon is blue in the active state (a pattern we also
use for bookmarks)
<img width="140" alt="image"
src="https://github.com/user-attachments/assets/13fb3f83-5639-49ca-9379-92617d8b2bbb"
/><img width="150" alt="image"
src="https://github.com/user-attachments/assets/0e35ef09-074a-46db-9aaa-d8ae087bb2fd"
/>


Before|After - positioning mirrors the timeline order
<img width="200" alt="image"
src="https://github.com/user-attachments/assets/cf5921e0-9c1f-45d3-9f15-7f8a5df382e8"
/>
<img width="200" alt="image"
src="https://github.com/user-attachments/assets/dc897c90-d207-4123-b42b-c46f8f75110e"
/>


Before|After - dropdown repositioned and restyled (wraps on mobile if
needed)
<img width="260" alt="image"
src="https://github.com/user-attachments/assets/c2c11465-70d1-4c8a-8147-9d96154c9ec8"
/>

<img width="600" alt="image"
src="https://github.com/user-attachments/assets/7f731891-1de2-4a30-ad0a-59d0bf0d9da7"
/>

Before|After - consistent language icon when setting composer language 
<img width="440" alt="image"
src="https://github.com/user-attachments/assets/ac83ab2d-b1fd-4b4e-9411-e65557a1582d"
/>

<img width="440" alt="image"
src="https://github.com/user-attachments/assets/49bd6660-d7c5-4515-8bb2-8ffe10590338"
/>



Before|after - Icon consistency in posts:
<img width="600" alt="image"
src="https://github.com/user-attachments/assets/04c0b8a7-4921-4d2e-8ddc-b2f0c7f20ae0"
/>

<img width="600" alt="image"
src="https://github.com/user-attachments/assets/1c2c99c0-5d83-4c57-8619-5fdb42ba9d38"
/>



Before|after - Post control is now consistent: 
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/ed5540a6-ae6e-4be9-b253-5bc8f2ad7377"
/>

<img width="420" alt="image"
src="https://github.com/user-attachments/assets/fa941b4c-7eb9-4bb8-b8e1-5f313ad30c17"
/>
2026-02-13 13:30:39 -05:00
Jordan Vidrine 0deca4caf6 FIX: Foundation feedback 3 (#37816) 2026-02-13 10:55:31 -06:00
Chris Alberti d34655f5c4 DEV: Add optional setting to include client_id in oidc logout endpoint url (#37818)
Adds support for including the `client_id` as a query parameter in the
endpoint for rp initiated logout. It's optional, turned on by a boolean
site setting, so we don't negatively impact existing users.

Meta: /t/387638

Customer requested this because although [it's optional in the
spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout),
their IdP requires it.
2026-02-13 10:47:51 -06:00
Loïc Guitaut 7cf341eea7 DEV: Bump required Ruby version to 3.4 (#37819)
Now that our base images ship Ruby 3.4, our Gemfile should require it as
the minimum allowed version.
2026-02-13 17:34:47 +01:00
David Taylor e74c2a7fee DEV: Silence pitchfork SOCK_SEQPACKET warning on macOS (#37815) 2026-02-13 16:30:05 +00:00
Rafael dos Santos Silva eef96f3e7d FIX: Assignment Instead of Comparison in semantic_categorizer.rb (#37817)
## Summary

Assignment (`=`) used instead of comparison (`==`) in
`SemanticCategorizer#categories` and `#tags` methods, causing
`pg_function` to always be mutated to `"<#>"` and incorrect score
adjustments to always be applied.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/255
- Original Commit:
https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/app/controllers/discourse_ai/admin/ai_embeddings_controller.rb

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-13 12:59:10 -03:00
Régis Hanol c0e1007941 FIX: show unread indicator for reply messages in DMs (#36918)
When someone replies to a message in a DM channel (where threading is
disabled), the system internally creates a thread to track the reply
chain. However, the unread count query was excluding all thread
messages, which meant these replies never showed up as unread in the
sidebar.

The fix has three parts:

1. Update ChannelUnreadsQuery to include DM thread replies in the
unread_count. We specifically check for DirectMessage channels with
threading disabled, and only count messages from other users that
haven't been read yet.

2. When creating a reply in a DM, add all channel participants to the
thread (not just the sender and OM author). This ensures everyone has a
thread membership that can be used for tracking read state.

3. When marking a DM channel as read, also mark any thread memberships
as read. Without this, the unread indicator would persist even after
viewing the channel since the thread membership's last_read wasn't being
updated.

Ref - https://meta.discourse.org/t/384734
2026-02-13 16:26:35 +01:00