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.
What is the problem?
The `Site` model stores `top_tags` and `category_top_tags` as plain
JavaScript objects (POJOs), but consumers often need Tag model instances
to access Tag-specific methods and properties.
What is the solution?
Add `topTags` and `categoryTopTags` getter properties to the `Site`
model
that lazily transform the underlying POJOs into Tag model instances
using
`store.createRecord()`.
What is the problem?
After commit 9e99066b07, `TagChooser` passes tag objects (with `id`,
`name`, `slug` properties) to its `onChange` callback instead of tag
name strings. The `TagList` site setting component was not updated to
handle this change, causing `changeSelectedTags` to call
`tags.join("|")`
on objects, which produces `[object Object]|[object Object]` instead of
the expected pipe-delimited tag names. This breaks settings like
`enable_solved_tags` in the discourse-solved plugin.
What is the solution?
Update `TagList#changeSelectedTags` to extract the `name` property from
each tag object before joining: `tags.map((t) => t.name).join("|")`.
Following the changes in #36678 where tags are now returned as objects
with {id, name, slug} instead of plain strings, this updates the
`AddCategoryTagClasses` component to handle both formats.
Screenshot with bug:
<img width="917" height="151" alt="Screenshot 2026-02-04 at 9 43 11 am"
src="https://github.com/user-attachments/assets/03171676-0512-4943-81fe-48d74dddfcd4"
/>
Previously, loadScript would silently fail when scripts or CSS files
failed to load, leaving callers with an unresolved promise. This made
error handling impossible and could cause retry logic to fail since
the URL remained in the _loading cache indefinitely.
Changes:
- Add error callback support to loadWithTag function
- Reject the promise with a descriptive error on load failure
- Clean up _loading cache on failure via finally() handler
- Clear onload/onreadystatechange handlers on error to prevent
duplicate callbacks
- Add unit tests for rejection and retry behavior
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.
When a whisper draft is re-opened, which can happen by opening a draft
from the drafts page or by hitting reply on a topic with an existing
draft, the whisper state was lost.
This PR fixes it.
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
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`
Add learn more URLs to all upcoming changes and rename the link to
"Feedback..." to encourage feedback from admins.
Add overall learn more URL for the upcoming change page to point
to the announcement topic.
This has been superseded by the newer isIpadOS device
detection in our capabilities service
https://github.com/discourse/discourse/pull/37470 should
be merged first to get rid of some other usages
When using `lazy_load_categories`, it is possible to open the composer
without _any_ pre-cached categories, which currently omits the Category
field from the composer.
This PR changes `showCategoryChooser` to consider `lazy_load_categories`
enabled as having `manyCategories`.
## 🔍 Overview
This update ensures that we fix some typos in the codebase, along with a broken CSS selector, accidental debug statements, and invalid references.
Converts the upsert-category panel components (appearance, general,
security, settings, tags) from legacy Ember patterns to modern Glimmer
components with proper Data Down, Actions Up (DDAU) architecture.
Changes include:
- Replace `buildCategoryPanel` base class with standard Glimmer
components
- Convert `@discourseComputed` decorators to native getters with
`@cached`
- Use `this.args.form.set()` for state updates instead of mutating
category
- Remove `TrackedObject` usage in favor of plain objects with spread
operators
- Extract `UpsertCategoryPermissionRow` as a standalone DDAU-compliant
component
- Use immutable array methods (`.toSorted()`, `.map()`, `.filter()`)
throughout
- Read form state from `transientData` for proper reactivity
Follow-up to #37339.
The previous fix addressed sorting when clicking the "sort by name"
button, but missed the initialization path when the
`tags_sort_alphabetically` site setting is enabled. Tags were still
being sorted by `id` instead of `name` on initial page load.
This also adds tests for both sorting paths (site setting and manual
toggle) to prevent regressions.
Ref - https://meta.discourse.org/t/394485
Related to
https://meta.discourse.org/t/admin-config-emoji-non-responsive-locks-up/394857
After uploading an emoji I can see an error on the index,
> Error: Assertion Failed: You attempted to update `_value` on
`TrackedStorageImpl`, but it had already been used previously in the
same computation.
Attempting to update a value after using it in a computation can cause
logical errors,
infinite revalidation bugs, and performance issues, and is not
supported.
`sort` mutates the array in-place, `toSorted` returns a new array
Technically returning undefined had a correct behavior in the code, but
given the naming of the function `hasPastEntries`, always returning a
boolean is more appropriate.
Found with automated AI review, all legit minor issues.
* Avoid notifying admins about upcoming changes they already
opted into
* Cache upcomingChanges getter in UI to avoid recreating TrackedObjects
* Fix previous_value not always stored in Toggle
Currently in several endpoints, we return an array of strings for tags.
Our goal with this PR is to expand array tag name strings to an array of
tag objects.
#### before: Tags were returned as string arrays
```
{ "tags": ["support", "bug-report"] }
```
#### after: Tags are returned as object arrays
```
{ "tags": [{"id": 12, "name": "support", "slug": "support"}, {"id": 13, "name": "bug-report", "slug": "bug-report"}] }
```
This allows us to start referencing tags by their ids, and return more
information for a tag for future features.
This commit involves updating several areas:
- topic lists (/latest.json, /top.json, /c/:category/:id.json, etc, for
`top_tags`)
- tag chooser components (`MiniTagChooser`, `TagDrop`, etc)
- topic view (/t/:id.json)
- tag groups (/tag_groups.json, tags, parent_tag)
- category settings
- staff action logs
- synonyms
- ...
APIs that reference tags based on their names will still be supported
with a deprecation warning. Moving on, we will reference them using
their tag ids.