Commit Graph
14088 Commits
Author SHA1 Message Date
Joffrey JAFFEUX dc51fcabcb FIX: raise 404 when sidebar section doesn't exist (#37675)
## Summary

`SidebarSectionsController#update` and `#destroy` return 403 Forbidden
instead of 404 Not Found when given a non-existent section ID.

## Source

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

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-10 15:47:49 +01:00
Natalie Tay 8a79c788a5 FIX: Do not write localized fancy title to db when fancy_title is null (#37668)
Related:
https://meta.discourse.org/t/yet-another-title-localization-issue/395469

### bug context

tldr; `Topic#fancy_title` saves a fancy_title to db when the fancy_title
is null.

This bug requires a certain incantation to trigger.
- Topic 395465 exists with `title = "Notification level button always
says \"tracking\""` and
`fancy_title = NULL` in the DB
  - A `TopicLocalization` exists for this topic in `zh_CN`
  - `content_localization_enabled` is on

When a crawler hits `GET /t/.../395465?tl=zh_CN`, localization
replacement happens on the topic, which writes the title attribute, so
the state is now
  - `title` = Chinese (modified)
  - `fancy_title` = NULL (untouched, still what was loaded from DB)
  
When serializing via `TopicViewSerializer` which uses
`LocalizedFancyTopicTitleMixin`, we call `topic.fancy_title`. The
`topic#fancy_title` generates the fancy_title from the title value, then
writes the chinese fancy title to db 😢


https://github.com/discourse/discourse/blob/e935ed63b28a30ee7ae6a7783ae05fe33edf3367/app/models/topic.rb#L532-L545

### fix

This commit fixes the issue by ensuring the fancy_title is always written
along with the title, preventing the need for invoking
`topic#fancy_title`.
2026-02-10 22:22:07 +08:00
Régis Hanol acd09a8972 FIX: Use post author username as fallback in notification emails (#37659)
When multiple replies are collapsed into a single notification,
`PostAlerter` sets `display_username` to a translated reply count (e.g.,
"7 réponses") for use in the notification dropdown UI. The
`original_username` field always holds the actual poster's username.

However, if `original_username` is ever missing from the notification
data, `notification_email` falls back to `display_username`, which
causes the reply count string to be used as the email sender username.
This leads to errors during email rendering.

This updates the fallback chain in `notification_email` to prefer
`post.user.username` over `display_username`, ensuring a real username
is always used even when `original_username` is absent.

https://meta.discourse.org/t//395420
2026-02-10 14:42:23 +01:00
Joffrey JAFFEUX 9694183f7c FIX: enforces logged in, in badges actions (#37666)
There was no actual security issue due to guardian methods, but it's a
better high level check to ensure user is logged in first.
2026-02-10 12:00:56 +01:00
Régis Hanol ca8fcb032f FIX: show sidebar "New category" option for moderators (#37624)
The sidebar "New category" action and the `/new-category` route were
checking `currentUser.admin` directly, which excluded moderators even
when the `moderators_manage_categories` site setting was enabled.

This is inconsistent with the backend Guardian which already allows
moderators to create categories when that setting is on.

Added `can_create_category` to `CurrentUserSerializer` (delegating to
`Guardian#can_create_category?`) and updated the sidebar component and
the new-category route to use it instead of manual role checks.

Ref - https://meta.discourse.org/t/395441
2026-02-10 11:50:11 +01:00
Régis Hanol 1e9524315e FIX: update bookmark UI when using keyboard shortcut (#37639)
When pressing B then Enter to bookmark a topic, the bookmark icon next
to the topic title and the topic footer bookmark button did not update
until page reload.

The keyboard shortcut path opens the BookmarkModal directly via the
topic controller, bypassing the TopicBookmarkManager that the footer's
BookmarkMenu component relies on. The afterSave callback correctly sets
`topic.bookmarked = true`, but the `topicBookmarkManager` computed
property only depended on `"topic"` (the object identity), so it never
recreated the manager to pick up the new bookmark state.

Changed the dependent key to `"topic.bookmarked"` so the manager is
recreated whenever the bookmarked property changes, regardless of which
code path triggered it.
2026-02-10 11:49:53 +01:00
Joffrey JAFFEUX 51c4266c35 FIX: user_count uses public_send with user-controlled input on SiteSetting (#37660)
## Summary

The `user_count` action in `Admin::SiteSettingsController` uses
`public_send` with user-controlled input, allowing invocation of
arbitrary ActiveRecord methods (like `default_scopes`) on `SiteSetting`
that start with `default_`.

## Source

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

---

🤖 Generated via [Patch Triage](https://patch.discourse.org/patch-triage)
2026-02-10 10:14:15 +01:00
Sam e26bc210a6 FIX: Log Discourse ID setting changes to staff action logs (#37649)
## Summary

The original commit added a new Discourse ID settings page with the
ability to enable/disable Discourse ID and regenerate credentials.
However, the `update_settings` action used direct assignment to
`SiteSetting.enable_discourse_id` which bypasses the staff action
logging mechanism, meaning changes to this setting were not being
recorded in the admin logs.
2026-02-10 18:18:35 +11:00
Sam 9e2d4f14d9 DEV: Missing requires_login for various actions (#37650)
General hygiene, we should require login from state changing routes and
not allow anon to end up getting an incorrect error message here.
2026-02-10 18:18:20 +11:00
Martin Brennan 8125ffa60a FIX: Stop impersonation session not working with group-based upcoming change (#37655)
Followup 42da6860fd

When the upcoming change for "Impersonate without logout" was added, we
didn't take into account that the "stop impersonation" action would not
work if the Staff enabled option for the upcoming change was used.

This was happening because in the ImpersonateController#destroy action
we werecheckingif  the current user had `impersonate_without_logout`
enabled, but we should be checking if the acting user had that
permission instead (i.e. the original admin not the user they are
impersonating)

c.f.
https://meta.discourse.org/t/new-bug-with-experimental-impersonation-interface/395621
2026-02-10 16:24:31 +10:00
Martin Brennan f1453e8a17 FEATURE: Filter settings by depends_on and link from upcoming changes (#37560)
This allows us to quickly link from the upcoming changes page
to any related settings, since sometimes after the change is enabled,
more settings may need to be configured. The current need for this
is the `enable_custom_splash_screen` setting.
2026-02-10 13:00:05 +10:00
Martin Brennan 6e8570b0fb DEV: Rename experimental_ upcoming change settings (#37589)
We can use the status of upcoming changes to indicate
whether they are experimental or not, having experimental
in the setting name is redundant.

Migrates the settings and the upcoming change events,
updates code, and updates yaml translation keys.
2026-02-10 10:34:37 +10:00
Martin Brennan 18833cf928 FIX: Simplified category Tabs allowed & tag group fields (#37591)
Both the allowed_tags and allowed_tag_groups fields were not
correctly saving in the Tabs form when simplified category creation
was enabled. This commit fixes the issue and adds a spec to cover
it.
2026-02-10 09:21:23 +10:00
Sam decf64f0d7 DEV: toggle_anon missing from requires_login (#37644)
The `toggle_anon` action in `UsersController` was missing from the
`requires_login` filter,
2026-02-10 08:36:27 +11:00
Régis Hanol a1cc7a2ba5 DEV: Add topic_count to UserSerializer staff attributes (#37632)
The UserSerializer exposes post_count as a staff attribute but does not
expose topic_count, even though the underlying data is available on the
UserStat model. This means consumers like the Discourse AI tool API's
discourse.getUser() function can retrieve how many replies a user has
made but not how many topics they have created, giving an incomplete
picture of user activity.

Add topic_count as a staff attribute in UserSerializer, mirroring the
existing post_count implementation. Also add spec coverage for all four
staff attributes (post_count, topic_count, can_be_deleted,
can_delete_all_posts) to verify they are included for staff scopes and
excluded for non-staff scopes.

Ref: https://meta.discourse.org/t/394878
2026-02-09 19:30:20 +01:00
Régis Hanol 4aed472271 FIX: Block associated account emails on user delete and anonymize (#37638)
When an admin deletes a user and blocks their email/IP, only primary
and secondary emails (from `user_emails`) were added to the screened
emails list. OAuth/social login emails (Google, Facebook, GitHub, etc.)
stored in `user_associated_accounts` were not blocked, allowing the
deleted user to re-register from a different IP using their OAuth
account.

Similarly, when anonymizing a user, invite cleanup, incoming email
deletion, and screened email IP anonymization only considered the
primary email, missing associated account emails entirely.

Collect emails from `user_associated_accounts.info['email']` alongside
`user_emails` using array union (`|`) for natural deduplication:

- `UserDestroyer`: block and clean up invites for all emails
- `UserAnonymizer`: capture associated emails before they are destroyed,
   pass them to the `AnonymizeUser` job
- `AnonymizeUser` job: use all emails for invite, incoming email, and
   screened email cleanup

Ref - t/173323
2026-02-09 18:26:35 +01:00
Renato Atilio c54250a7e7 UX: add name to wizard radios for keyboard nav (#37615)
Introduces a minor accessibility improvement to the radio button group
in the wizard component and adds a corresponding system test to ensure
correct keyboard navigation behavior.
2026-02-09 09:43:32 -03:00
Renato Atilio 4ecf3a0d1e FIX: prevent cleanup_trackers from unintentionally deleting all trackers (#37619)
Ensures that no `ProblemCheckTracker` records are deleted when there are
no current targets (`not: []` will resolve to `1=1`) and adds a test to
verify this behavior.
2026-02-09 09:43:13 -03:00
Alan Guo Xiang Tan c48f18dbed FIX: Resolve tag synonyms in search and filter queries (#37628)
What is the problem?

Discourse allows admins to mark a tag as a synonym of another tag. For
example, "brunch" can be made a synonym of "lunch". When this happens,
all topics tagged with "brunch" are automatically retagged with "lunch",
and the `tags.target_tag_id` column on the synonym tag record is set to
point to the target tag.

However, several code paths did not account for synonyms:

1. **Search:** `Search#search_tags` and the hashtag advanced filter did
   not resolve synonyms. When a user searched using a synonym name (e.g.
   `tags:brunch`, `tags:brunch+eggs`, or `#brunch`), no results were
   returned because:
   - The `tags:` comma path queries `topic_tags` joined with `tags` by
     name, but topics are tagged with the target tag "lunch", not the
     synonym "brunch".
   - The `tags:` plus path aggregates tag names per topic into a
     tsvector and matches against the searched name, but the aggregated
     names are target tag names, so "brunch" never matches.
   - The `#` hashtag path picks the synonym tag's own `id` and queries
     `topic_tags` by that ID, but topics store the target tag's ID.

2. **Filter route:** `TopicsFilter#tag_ids_from_tag_names` concatenated
   both the synonym's own ID and the target tag ID. For match-all
   queries (e.g. `tag:brunch`), this required a topic to have both IDs
   in `topic_tags`, which never happens since only the target tag ID is
   stored. For negation queries (e.g. `-tag:brunch`), the exclusion
   targeted the synonym ID rather than the target, so no topics were
   excluded.

What is the solution?

In `Search#search_tags`, add a synonym resolution step before the
existing comma/plus branching logic. It splits the match string into
individual tag names, queries for any that are synonyms via
`Tag.where_name(tag_names).where.not(target_tag_id: nil)`, builds a
name mapping, and replaces synonym names with their target tag names.
The replacement operates on the split array elements rather than using
substring replacement to avoid corrupting tag names that may contain
other tag names as substrings.

In the hashtag advanced filter, pick both `:id` and `:target_tag_id`
from the tag lookup and prefer `target_tag_id` when present, so the
query uses the target tag's ID instead of the synonym's own ID.

In `TopicsFilter#tag_ids_from_tag_names`, replace the transpose/concat
approach with `.map { |id, target_id| target_id || id }` to resolve
each tag to its canonical ID — the target for synonyms, or the tag's
own ID otherwise.

A partial index on `tags.target_tag_id` is added (scoped to
`WHERE target_tag_id IS NOT NULL`) to support efficient synonym lookups.
2026-02-09 15:57:52 +08:00
Natalie Tay 6cae7a3a7e DEV: Remove tag_id param in favour of tag param for topic creation with tags to allow tag creation (#37597)
In https://github.com/discourse/discourse/pull/36678 we introduced the
`tag_ids` param for topic creation and updating to add tags to a topic,
however, this does not cater for new tags which are allowed based on the
UI and previous API.

Furthermore, the frontend wasn't even using the new param, making the
new `tag_ids` implementation on the controller moot.

This commit 
- removes this `tag_id` param
- updates the deprecation note to the `tags` param to require `[{ id: 1,
name: "old1" }, { name: "new1" }]` rather than the old `["old1",
"new1"]`.
- including IDs here eliminates the case where John renames `"old1"` to
`"old2"`, but Mary sends `["old1", "new1"]`, thus creating the `"old1"`
tag again when it was already renamed.
  - keeps us consistent with other endpoints that take in `tags`
  
Reviewer note: The `tag_topic_by_names` method is [extremely
complicated](https://github.com/discourse/discourse/blob/2b24fc91c1f46e57913d6acb7aee0c05041f64be/lib/discourse_tagging.rb#L19-L248).
Ideally, we would like to have a `tag_topics` method that does not incur
that many `Tag` queries, but to prevent a large refactor here, we are
re-using `tag_topic_by_names`.
2026-02-09 14:48:58 +08:00
Régis Hanol ce9abeb50d FIX: Handle Nokogiri HTML parser limits in PrettyText.excerpt (#37610)
The /drafts endpoint returns a 500 error when any draft contains HTML
with excessive nesting depth or too many attributes per element.

Nokogiri::HTML5.fragment raises ArgumentError when these limits are
exceeded, and PrettyText.excerpt had no error handling for this. A
previous fix in PostItemExcerpt only caught the tree depth variant,
leaving the attributes limit unhandled, and only protecting one of the
13+ callers.

Rescue ArgumentError around the Nokogiri::HTML5.fragment call in
PrettyText.excerpt and return "" on failure. This is consistent with the
existing blank-input guard and protects all callers at once. The
now-redundant rescue in PostItemExcerpt is removed.

Ref - t/173858
2026-02-06 22:00:54 +01:00
Renato Atilio 5bfc9764f7 FIX: Handle empty table bodies in rich editor paste (#37603)
https://github.com/discourse/discourse/commit/c35e7366 improved handling
of pasted tables in the ProseMirror rich editor by normalizing column
counts and supporting nested tables. However, it introduced a potential
crash when pasting tables with empty tbody elements or rows with zero
cells.

This PR fixes this edge case.
2026-02-06 12:12:51 -03:00
Renato Atilio 1a86043ddf FIX: rich editor base64 replacement position (#37602)
On the rich editor automatic download of base64 image data, image
positions retrieved from `dataURIMap` are now sorted in descending order
before replacement. This prevents issues when replacing multiple images
with the same data URI by ensuring that later positions are replaced
first, avoiding offset errors.
2026-02-06 12:12:25 -03:00
David Taylor 04d28fd34a FIX: GitUtils preventing boot (#37599)
GitUtils is called very early, even before `Rails.root` is available.
Use `__dir__` to determine the directory instead.
2026-02-06 13:07:28 +00:00
David Taylor 339746a413 DEV: GitUtils overriding via config/git-utils-overrides.json (#37571)
`GitUtils` determines the Discourse version information which is shown
in the user interface. This commit introduces an optional
`config/git-utils-overrides.json` file, which hosting providers can use
to override this information. For example, if the hosting provider is
needs to apply hosting-platform-specific patches or embargoed security
fixes on top of a normal release branch.

Previously there were no specs for this file. So this commit introduces
specs for the default behaviour, and the new overrides feature.
2026-02-06 11:45:33 +00:00
Natalie Tay 2474fc87e7 FIX: Preserve case when bulk creating tags (#37593)
Bulk tags feature introduced in
https://github.com/discourse/discourse/pull/36645 was lower-casing tags
being sent, regardless of the `force_lowercase_tags` setting.

This commit ensures the lowercase is done only when that setting is enabled.
2026-02-06 17:12:07 +08:00
Alan Guo Xiang Tan 8750ae2e18 FIX: allow new tags to be created from the Tag Group editor (#37594)
What is the problem?

When creating or editing a tag group, users can type new tag names into
the `TagChooser` (which has `allowAny=true`). The select-kit component
assigns a string-based ID to these new tags. `TagGroupsController#tag_groups_params`
blindly extracts `id` from each tag object, producing the string name
instead of a database ID for new tags. `TagGroup#tag_ids=` then silently
ignores these invalid values, so the new tags are dropped without error.
The same issue affects parent tags.

This was introduced in 9e99066b07 which changed tags from string arrays
to object arrays but did not handle the case where new tags have no
numeric ID.

What is the solution?

On the frontend, the `TagGroupsForm#save` action now strips non-numeric
IDs from tags and parent tags before sending to the backend via a private
`#serializeTag` method. Tags with a numeric `id` (existing) keep both
`id` and `name`; tags with a string `id` (new) are sent with only `name`.

On the backend, `TagGroupsController#tag_groups_params` now splits the
tags array into existing (have `id`) and new (no `id`). New tag names are
passed to `DiscourseTagging.find_or_create_tags!` which validates names via
`tags_for_saving` and creates them. The same logic applies to parent
tags.
2026-02-06 14:27:01 +08:00
Martin BrennanandLoïc Guitaut 43ccc4def0 FIX: Logging hole in group user histories and convert group create to service (#37054)
When creating groups, we weren't logging the group owner
as being the owner in group histories, nor were we logging
them being added to the group. So it was not clear in the user
logs where this user ever came from. This PR fixes the issue,
and also converts the admin group create endpoint to use a
service.

Now the logs look like this when a group is created:

<img width="830" height="378" alt="image"
src="https://github.com/user-attachments/assets/871e319a-5512-411e-8565-3b63803e688c"
/>


c.f.
https://meta.discourse.org/t/logging-hole-for-group-histories/392942

---------

Co-authored-by: Loïc Guitaut <loic@discourse.org>
2026-02-06 13:34:58 +10:00
Jordan Vidrine 0fd684ba52 DEV: Fix spec (#37585) 2026-02-05 15:19:41 -06:00
Kris fb26fc66a4 FIX: support nested descriptions in object settings (#37538)
Reported here:
https://meta.discourse.org/t/labels-and-descriptions-missing-from-nested-object-settings/394685

Descriptions for nested object settings were not being displayed due to
a mismatch in locale key formatting. Stripping `.schema.properties.` so
that the locale keys from the serializer match the keys expected in the
template fixes it

Before: 
<img width="600" alt="image"
src="https://github.com/user-attachments/assets/1e0ab4c6-e6eb-478d-97bc-76f16739135b"
/>


After: 
<img width="600" alt="image"
src="https://github.com/user-attachments/assets/a9a9e64c-4efa-4807-94a1-dd853ffd9f04"
/>
2026-02-05 14:01:58 -05:00
Régis Hanol 6cd48ccd23 FIX: Simplified category creation validation and state reset (#37567)
Two issues addressed:

1. Require icon selection when style type is "icon" - previously the new
category interface allowed creating categories without an icon, unlike
the old interface which required one.

   https://meta.discourse.org/t/395223

2. Reset security settings when navigating from edit to new category -
the categoryVisibilityState was being reused across different
categories, causing the security tab to show the previous category's
settings.

   https://meta.discourse.org/t/395224
2026-02-05 19:21:06 +01:00
Osama Sayegh 47ca25d56e FIX: Eager load base_scheme color_scheme_colors in themes controller (#37550)
When a color scheme has a base_scheme, the base scheme's
color_scheme_colors were not being eager loaded, causing strict loading
violations.

This updates the includes statement to properly load the nested
association `base_scheme: :color_scheme_colors` instead of just
`:base_scheme`.

Also adds a test that creates color schemes with base_scheme
relationships to verify the associations are properly loaded without
strict loading errors.
2026-02-05 13:17:56 +03:00
Natalie Tay 73e3286356 FIX: Cache banners per locale (#37561)
Currently topic banners are localizable, but unfortunately there is a
global cache that is not scoped per locale.

This commit fixes that.
2026-02-05 17:21:12 +08:00
Loïc Guitaut 02b3407b5a FIX: Complete protection for potentially illegal reviewables on post deletion (#37536)
This is a follow-up to fd04f690.

The original commit only protected potentially illegal reviewables from
being auto-approved in one code path (direct post deletion). However,
when deleting a post through a reviewable action with
`notify_users_after_responses_deleted_on_flagged_post` enabled, the code
would still call `#ignore` on any flagged reply posts without checking
`#potentially_illegal?`.
2026-02-05 09:42:52 +01:00
Alan Guo Xiang Tan 6db95a02c7 DEV: Add system test for tag-list site setting (#37559) 2026-02-05 15:47:47 +08:00
Sam 2e3d01ae0b FIX: incorrect Breton plural rule logic operator (#37545)
Technically there appear to also be a two and many... but this at least
removes some of the bug.

```
def wrong(n)
  n != 13 || n != 14
end

def right(n)
  n != 13 && n != 14
end

[13, 14, 15].each do |n|
  puts "#{n}: wrong=#{wrong(n)}, right=#{right(n)}"
end
```

15 is (not 13) OR (not 14)
15 is not (not 13) AND (not 14)

apologies for the headache.
2026-02-05 18:04:59 +11:00
Natalie Tay debac2fd5b FIX: Ensure composer also filters tags when searching (#37555)
When editing a topic via the composer, tags with parent tag requirements
weren't appearing in search results, even though the parent tag was
already selected on the topic. When the composer opens for editing, it
was using these serialized string tags. The mini-tag-chooser couldn't
extract tag IDs from strings

This commit fixes that. Also adds a system spec testing the composer and
title header scenarios.
2026-02-05 15:03:15 +08:00
Sam 73e021abda FIX: Complete underscore support in topic tag filter regex (#37552)
Adds support of `tags:hello_world,hello_not_world`
2026-02-05 17:59:53 +11:00
Sam e60890dbae FIX: broken XPath escaping and undefined CSS constant in anchor lookup (#37546)
The original commit added anchor fragment enhancement for onebox
descriptions, reusing the `find_anchor_target` method. However, this
method had two bugs: a broken string escaping pattern that corrupts
fragments containing single quotes, and a reference to an undefined
`CSS.escape` constant that would raise a `NameError` at runtime.
2026-02-05 17:59:35 +11:00
Sam d9c414e645 FIX: Add missing error handling to merge_topic hijack block (#37542)
The original commit moved the topic merging operation into a `hijack`
block to avoid Unicorn worker timeouts when merging topics with many
posts. However, it omitted error handling that exists in the similar
`move_posts` method, which could lead to 500 errors with empty bodies in
production when `ActiveRecord::RecordInvalid` or
`ActiveRecord::RecordNotSaved` exceptions occur.

Without this we will get no information about why merging did not work
2026-02-05 17:46:16 +11:00
Natalie Tay bc570b3751 FIX: Accept booleans for whisper, is_warning, no_bump, shared_draft when creating a post (#37551)
Due to the following,


https://github.com/discourse/discourse/blob/e0d8ba788e957787a1cb06f856c9c15253c4eb9e/app/controllers/posts_controller.rb#L956-L963

we currently only check for `whisper == "true"` and not `whisper ==
true` when creating a post. This also applies for the attributes
is_warning, no_bump, shared_draft.

This commit ensures that booleans and strings `true` are accepted.
2026-02-05 14:45:04 +08:00
Sam 20811cc9c0 FIX: exclude 't' shortcut from min length bypass (#37440)
The 't' shortcut (in:title) modifies where to search but still
requires actual search terms to produce meaningful results. Unlike
'l' (latest) and 'r' (recent) which return results on their own,
't' alone should enforce the minimum search term length.

Renames `valid_search_shortcut?` to `min_length_bypass?` to better
describe the method's purpose.
2026-02-05 06:58:19 +11:00
Régis Hanol 471b1a662c FIX: auto-switch to private when selecting restricted parent category (#37530)
When creating a new category and selecting a restricted parent category,
the visibility should automatically switch to "Private" mode.

Previously, if a user toggled between Public/Private before selecting a
restricted parent, the UI would incorrectly stay on "Public" even though
the parent category required restricted access.

This commit:
- Resets visibility state when selecting a restricted parent category
- Resets to public when removing the parent category
- Adds controlled mode to ConditionalContent component (when @onChange
is provided, the component respects external @activeName changes)
- Removes dead code (userModifiedPermissions)
- Uses DToggleSwitch page object in category specs
2026-02-04 16:05:18 +01:00
Régis Hanol ad53518ec6 FEATURE: add filter to admin badges list (#37527)
Adds a search/filter input to the admin badges page, allowing admins to
quickly find badges by name or description instead of scrolling through
a long list.

Ref - https://meta.discourse.org/t/395044

## Example of using the badge filter

<img width="1458" height="1203" alt="2026-02-04 @ 12 20 06"
src="https://github.com/user-attachments/assets/69be8ae1-affb-4ca4-b34a-792eca1760cf"
/>
2026-02-04 16:05:03 +01:00
Martin Brennan 45776f7f1c FIX: youtu.be URLs with parameters not using lazy-video in Chat (#37473) 2026-02-04 08:49:33 +01:00
Natalie Tay 2d7f8062fa FIX: User preferences page for tracking tags should show tag name (#37517)
Reported here:
https://meta.discourse.org/t/tag-ids-displaying-instead-of-slugs/395070

The fix ensures that we return the names and not just the IDs
2026-02-04 15:47:50 +08:00
Alan Guo Xiang Tan 02fd694014 UX: Count all reviewables on user profile flags counter (#37402)
What is the problem?

The flagged posts count on user profiles only shows
`ReviewableFlaggedPost` items
and excludes other reviewable types like `ReviewableQueuedPost`,
`ReviewableUser`,
and plugin-provided reviewables (chat, AI, etc.).

`User#number_of_flagged_posts` queries only `ReviewableFlaggedPost`
records, and
the review queue link includes `type="ReviewableFlaggedPost"` as a query
parameter
to scope results. This has been confusing for staff who expect the count
to reflect
all pending review items for a user, not just flagged posts.

What is the solution?

Product has decided to change the label from "X flagged posts" to "X
flags" and
stop scoping to just `ReviewableFlaggedPost`, so the counter reflects
all
reviewables for the user.

1. Rename `User#number_of_flagged_posts` to `User#number_of_flags` and
query
   all `Reviewable` records instead of just `ReviewableFlaggedPost`
2. Remove `type="ReviewableFlaggedPost"` from the review queue link
query params
so clicking through shows all reviewables matching the username filter
3. Rename the translation key from `flagged_posts` to `flags` and CSS
class from
   `.flagged-posts` to `.flags`
2026-02-04 14:04:57 +08:00
Régis Hanol e08cf7e8a9 DEV: Add system specs for simplified category creation (#37493)
Adds comprehensive system tests for the simplified category creation
feature covering all tabs: General, Security, Settings, Images, and
Tags.

Also includes:
- Fix FormKit `choose_conditional` to work with hidden radio inputs
- Add `toggle_advanced_settings` and `toggle_checkbox` helpers to
Category page object
2026-02-04 10:32:31 +10:00
Régis Hanolandawesomerobot 9cb85906b8 DEV: Refactor category permissions to use group IDs (#37483)
This change improves the reliability of category permission management
by using group IDs instead of group names for all operations.

Changes include:
- Use group_id for permission add/remove/update operations
- Better handling of subcategory permissions inherited from parent
- Add granular permission messages (full/reply/see-only access)
- Load parent category permissions when editing subcategories
- Remove unused methods and simplify permission logic

## When selecting a restricted parent category, we mark the current
category as private, disable the group selector and link to the
"advanced security" tab

<img width="1487" height="1237" alt="2026-02-03 @ 16 11 17"
src="https://github.com/user-attachments/assets/add9444f-0cd6-414a-b307-80ba3af8e811"
/>

## In the "advanced security" tab, I've improved the message displayed
when adding the "everyone" group

<img width="1487" height="1237" alt="2026-02-03 @ 16 11 37"
src="https://github.com/user-attachments/assets/3d628504-af25-412d-b5a3-956fa1c6769e"
/>
<img width="1487" height="1237" alt="2026-02-03 @ 16 11 41"
src="https://github.com/user-attachments/assets/9225e47b-b6f6-4fa3-97c5-4877df314e47"
/>
<img width="1487" height="1237" alt="2026-02-03 @ 16 11 43"
src="https://github.com/user-attachments/assets/2cddd8ff-380e-4ecd-a17c-3e0adaac55a3"
/>
<img width="1487" height="1237" alt="2026-02-03 @ 16 11 46"
src="https://github.com/user-attachments/assets/1a23ec2d-8950-449e-b99d-a057b3a60e61"
/>

---------

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
2026-02-03 22:08:32 +01:00
Jarek Radosz 844bd89719 FIX: Add a page limit to directory_items endpoint (#37496) 2026-02-03 20:02:44 +01:00