Previously, when rebaking posts containing GitHub PR oneboxes, we passed
`invalidate_oneboxes: true` to `rebake!` which would re-fetch the onebox
during cooking. This meant each post independently fetched the same PR
data from GitHub.
Now we:
1. Fetch and cache the onebox once upfront via `Oneboxer.preview`
2. Use the new `skip_publish_rebaked_changes` parameter to prevent
publishing intermediate states, without triggering onebox re-fetches
3. Filter posts by `cooked LIKE '%githubpullrequest%'` in SQL rather
than fetching all linked posts and filtering in Ruby
The new `skip_publish_rebaked_changes` parameter on `Post#rebake!`
allows callers to skip the `:rebaked` publish without triggering onebox
invalidation, which is useful when the caller has already refreshed the
cache separately.
This will allow us to invalidate the Rewind cache when making
changes to reports, in case of security or other fixes.
Also refactors the shared logic in FetchReport and FetchReports
into a helper module.
Previously we were not clearing title counter notification callbacks on
cleanup in test
this could lead to getting flakes (and progressively slower acceptance
tests) as we keep adding
adding more and more Title counters.
The title counter is an interface that lets plugins add counts to title.
Convert `findPostsByIds` and `loadIntoIdentityMap` to async functions in
`PostStream` to resolve a race condition when loading posts. This
ensures proper sequencing of asynchronous operations and improves
reliability in loading post data.
This PR addresses the issue reported in
https://meta.discourse.org/t/the-web-page-continuously-loads-previous-posts-and-automatically-scrolls-up-quickly/391192
There are no tests added because I can't find a way to reliably
reproduce the issue in tests and verify it's properly fixed.
We had a few places where we were referring to `currentUser` instead of
the user being viewed. This was resulting in some wrong info being shown
when viewing someone else's rewind.
Adding a few missing button classes and updating the AI features buttons
from "edit" to "settings" — as it's actually a list of settings rather
than an editing experience.
Refactor `discourse-bootstrap.js` to use `importSync` when loading
`setupDeprecationCounter`, improving module dependency handling. This
ensures the code gracefully handles environments where the deprecation
counter module is unavailable.
Following idle pane refactoring we incorrectly were expecting the
scroller from the scrollable-list when we now expect the component
(chat-channel/chat-thread) to pass it down.
We have tests for this behavior generally but testing the exact
scrolling moment is generally brittle, I won't add another test for now.
This PR implements stricter deprecation handling that enforces
deprecation-free tests for core and preinstalled plugins, while allowing
custom (non-preinstalled) plugins and themes to have deprecations
without causing test failures.
### Key Changes
#### CI Workflow Improvements
- Split plugin system tests into separate CI targets: `core-plugins`,
`official-plugins`, and `chat`
- Enhance `bin/turbo_rspec` to accept comma-separated exclude patterns
via `--exclude-pattern`
- Simplify workflow configuration with default `shell: bash` and
consolidated environment variables
#### Plugin Classification & Detection
- Centralize official plugins list in `config/official_plugins.json` for
unified backend and frontend access
- Detect preinstalled plugins by checking for absence of `.git`
directory
- Add `isOfficial` and `isPreinstalled` metadata flags to plugin info
- Add `data-preinstalled` and `data-official` attributes to all plugin
and theme script tags for runtime identification
#### Deprecation Source Tracking
- Track deprecation sources (core, plugin, or theme) through template
map and resolver to attribute deprecations correctly
- Improve `source-identifier.js` to detect admin UI plugin files in both
development and production environments
- Add source information to deprecation messages for better debugging
#### Test Infrastructure
- Modify `raise-on-deprecation` test helper to skip errors for custom
(non-preinstalled) plugins and themes
- Add `EMBER_RAISE_ON_DEPRECATION` environment variable to control
deprecation throwing behavior in Rails tests
- Automatically set `EMBER_RAISE_ON_DEPRECATION` for core and
preinstalled plugin/theme specs in `rails_helper.rb`
- Improve deprecation summary output for system specs with test/spec
origin tracking
#### Deprecation Workflow Enhancements
- Add `dont-throw` handler for selective deprecation bypassing in test
fixtures without raising errors
- Add `dont-count` handler for preventing deprecation counting in
specific scenarios (e.g., test fixtures)
#### Deprecation Fixes
- Fix pending deprecations across core plugins (chat, data-explorer,
discourse-subscriptions, gamification, house-ads, reactions,
rss-polling, styleguide)
- Update import paths and remove deprecated patterns
- Migrate deprecated Handlebars templates to JavaScript API
### Testing Strategy
With these changes:
- **Core and preinstalled plugins** must pass all tests without any
deprecations
- **Custom plugins and themes** can have deprecations without failing
tests
- Test fixtures can use `dont-throw` and `dont-count` handlers when
testing deprecation behavior itself
- System specs automatically configure deprecation enforcement based on
test file location
---------
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
When clicking "Add to Google Calendar" for an event with a specific
timezone, the generated UTC times were incorrect. The code was parsing
the date string using the user's local timezone instead of the event's
timezone.
For example, an event at 18:30 in a UTC+2 timezone would be incorrectly
converted as if it were 18:30 in the user's local timezone (e.g.,
UTC-4), resulting in a 6-hour error in the Google Calendar URL.
The fix passes the event's timezone to `_formatDateForGoogleApi` and
uses `moment.tz()` to parse dates in the correct timezone before
converting to UTC. Also cleaned up the date formatting to use moment's
format() method instead of regex replacement.
Internal ref - t/162288
RFC5735 prescribes networks for use in tests and documentation and we should
use those instead of sending connection attempts to Australia every time our
test suite runs.
Without this, these variables when set in the environment would be passed as
strings, causing an error:
```
NoMethodError: undefined method `zero?' for an instance of String (NoMethodError)
return yield(sec) if sec == nil or sec.zero?
^^^^^^
from …/ruby-3.3.6@discourse/gems/timeout-0.5.0/lib/timeout.rb:203:in `timeout'
```
The mail gem upgrade to 2.9.0 picked up a gem change that causes it to error
when both `tls` and `starttls_auto` are set, despite this being a valid
configuration.
This change causes the `enable_starttls_auto` setting to only be true if `tls`
is not set.
Functionality demonstration:
```
→ DISCOURSE_USE_SMTP_ENVIRONMENT_IN_DEVELOPMENT=true \
DISCOURSE_SMTP_ADDRESS=localhost \
DISCOURSE_SMTP_USER_NAME=bilbo \
DISCOURSE_SMTP_PASSWORD=ring \
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE=none \
DISCOURSE_SMTP_PORT=5465 \
DISCOURSE_SMTP_FORCE_TLS=true \
rake 'emails:test[frodo@shire.net]'
Testing sending to frodo@shire.net using localhost:5465, username:bilbo with plain auth.
SMTP server connection successful.
Sending to frodo@shire.net…
Mail accepted by SMTP server.
Message-ID: 79e7aafd-e2e7-4667-9b29-b01aa2d28bcf@localhost
```
```
→ DISCOURSE_USE_SMTP_ENVIRONMENT_IN_DEVELOPMENT=true \
DISCOURSE_SMTP_ADDRESS=localhost \
DISCOURSE_SMTP_USER_NAME=bilbo \
DISCOURSE_SMTP_PASSWORD=ring \
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE=none \
DISCOURSE_SMTP_PORT=5587 \
rake 'emails:test[frodo@shire.net]'
Testing sending to frodo@shire.net using localhost:5587, username:bilbo with plain auth.
SMTP server connection successful.
Sending to frodo@shire.net…
Mail accepted by SMTP server.
Message-ID: 40568ecb-ce4e-4373-aa2e-6cdf59ead158@localhost
```
The PostList component already handles footer visibility internally via
the {{hideApplicationFooter}} helper based on its own canLoadMore state.
Setting showFooter on the application controller was redundant and
triggered a deprecation warning.
Also removes the unused `@controller` application injection.
Previously, emoji-value-list had its own CSS rule using `.desktop-view`
to hide reorder buttons. This meant buttons were hidden on tablets even
though they support touch (where hover isn't available).
This removes the duplicate `.desktop-view .emoji-value-list` CSS block
and lets emoji-value-list inherit the shift button behavior from
`.value-list`, which already uses `.discourse-no-touch` to properly
detect touch capability (added in #34102).
This ensures reorder buttons are:
- Hidden until hover on non-touch devices
- Always visible on touch devices (including tablets in desktop view)
Also adds system specs to verify reorder button visibility for both
simple-list and emoji-list settings on touch and non-touch devices.
Ref - https://meta.discourse.org/t/391181
Prevents stuck thread notifications for AI bot channels that force
thread creation (ie. `forced: true`). Even when a channel has set
threading disabled, bots will still create threads. Those threads won't
appear within the unread thread list page/tab, but they do result in a
blue dot on the sidebar/drawer tab etc.
This change just prevents the blue dot notification as there is no way
to clear it otherwise.
Whenever the `HorizontalOverflowNav` component is rendered, we call
`scrollIntoView` on the current active element to ensure that the link
which is currently active will be shown to the user. However, we were
calling `scrollIntoView` with the `container` option defaulting to `all`
which impacts all scrollable containers.
Per mozilla docs,
> All scrollable containers are impacted (including the viewport).
This is not ideal as it can cause unintended scrolls on the viewport
thus resulting in a bad UX. To resolve this, this commit adds the
`container:
"nearest"` option when we call `scrollIntoView` which ensures that only
the nearest scrollable container to the active element is impacted.
This problem was discovered while I was looking at review queue index
page and noticed that my scroll position was always lost when new
reviewable items are added to the page. It turns out that the review
queue index page renders the `reviewable-refresh/item.gjs` component
which then renders the `horizontal-overflow-nav.gjs` component.
Followup 5ec959c72c
Since Rewinds can be public, we also need to limit the following:
* Do not show tags in restricted groups for Most Vierwed Tags. Instead,
only show tags that anon will be able to see, so tags with no or
low permissions.
* Do not show Most Viewed Categories that are restricted categories
* Do not show whispers in Best Posts
This is a system spec commit that covers the following core user journeys
surrounding tags:
- covers main routes (`/latest`, `/tag/tag-name`, `/c/category`,
`/tags/c/cat-slug/cat-id/tag-name`)
- tag-drop component
- sidebar nav
- topic and PM creation with tags
- mini-tag-chooser component
Also noting that some assertions (only a handful) might already exist,
but it is a good central place to ensure basic tag features are
functional. Prior to this spec, there have been edits made to
implementation with zero tests failing.
I've chosen to merge 2 long flows instead of separating them into
several, as the tests run much faster (11s vs 18s) without multiple
`it`.
Handle, timeout of user so we stop reading and scrolling when user is in
background or no longer active
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: David Taylor <david@taylorhq.com>
This is a small PR just enabling the refreshed review page for everyone.
At this stage, I don't want to remove the site setting so we can
potentially quickly revert that change.
I will create a follow-up PR that will remove the hidden site setting
and all legacy code.
Some tests were irrelevant. For example, in the old layout, long posts
were collapsed. In the new layout, we have an inline scroll.
<img width="1143" height="679" alt="Screenshot 2025-12-16 at 12 37
37 pm"
src="https://github.com/user-attachments/assets/3020dfd1-f84b-44ca-906f-385536bdce3d"
/>
This commit introduces a toggle which is enabled by default
to share the Discourse Rewind report publicly.
**IMPORTANT NOTES**:
* Users with "Hide my public profile" enabled
**will not** have their Rewind shared publicly by default,
and will not be able to unless they disable "Hide my public
profile" first.
* This commit also excludes personal messages from best
posts and best topics reports so sensitive message data
will never be shown, but other report data like reaction
counts may still be based off of PM data
Other users who cannot see the rewind will see an error.
We always show a message saying which user you are looking
at when viewing another user's Rewind.
Users can also unshare their rewind from Rewind preferences.
1. Add a ... animation to the thinking blocks so you can tell llm is
thinking
2. Improve design of native tool vs xml tool handling which was uneven
across providers
3. Exit chains and stop asking for tools when we are out of tool budget
---------
Co-authored-by: Keegan George <kgeorge13@gmail.com>
When saving a category with default "everyone" permissions in
non-English forums, staff action logs incorrectly showed a permission
change even when nothing changed.
The issue occurred because the backend fallback for empty permissions
used the hardcoded English string "everyone", but the frontend sends the
localized group name (e.g., "jeder" in German). This mismatch triggered
false log entries.
Changed the fallback in categories_controller.rb to use
Group[:everyone].name which returns the actual localized group name from
the database.
Ref - https://meta.discoure.org/t/335600/14
When selecting a model or feature filter on the LLM Usage page, the dropdown
would lose its options and display incorrect values (like "0") because
fetchData() was clearing the cached dropdown options on every request.
The filtered API response only contains models/features matching the filter,
so rebuilding the dropdown options from filtered data caused other options
to disappear.
Added a `clearCache` parameter to fetchData():
- Initial load and date range changes pass `clearCache: true` to refresh options
- Filter changes use the default `clearCache: false` to preserve all options
Ref - https://meta.discourse.org/t/391273