PR #40214 dropped this step to avoid re-downloading Chromium on every
system test job, since the discourse_test image already bakes in a
matching browser. That assumption breaks whenever package.json bumps the
playwright version ahead of the image: the pinned npm package then
expects a browser revision the image doesn't have, and every system test
fails with "Executable doesn't exist".
`playwright install` already skips browsers it finds already present for
the current version, so restoring the step costs nothing when the image
is in sync and downloads the right browser when it isn't.
Adds a dependabot group for @warp-drive/* npm packages, following the
same pattern as the existing embroider, codemirror, and uppy groups, so
their version bumps land in a single PR instead of one per package.
- Match pnpm setting to dependabot, so we get the same behavior
regardless of update mechanism
- Add exception for `discourse`, which is the internal alias for
`@discourse/types`
This should get dependabot working again since we bumped
`@discourse/types`
processTree() in dts-generator (used to bundle each vendored package's
.d.ts into @discourse/types) replaces certain nodes, like the `declare`
keyword, with an empty string. TypeScript scans a node's `pos` to
include its leading trivia (blank lines, comments), but skip() jumps
straight to `node.end` on replacement, so that trivia got silently
deleted along with the token being replaced.
This surfaced concretely in @floating-ui/dom's type declarations, where
`export { Axis }` was immediately followed by a blank line and `declare
type BivariantCallback = ...`. Removing `declare` also removed the blank
line, merging the two statements onto a single line and producing
invalid TypeScript in the generated
external-types/floating-ui__dom/index.d.ts (confirmed by PR #43280,
which bumped to a build containing this and failed `pnpm lint:types`
with `TS1005: ';' expected`).
The fix emits the node's leading trivia before applying the replacement,
so only the token itself is swallowed.
Since parameters bind as native Postgres bind parameters rather than
being spliced into the SQL text, an unresolved-type parameter used
somewhere like a bare `:param IS NULL` makes Postgres fail with "could
not determine data type of parameter $N", because Postgres can't infer a
type for a bind parameter the way it can for a plain literal.
Data Explorer already knows each parameter's declared type from the
query's `-- [params]` block, so this uses it to give `string`,
`string_list`, `date`, and `datetime` parameters an explicit Postgres
type instead of leaving them unresolved. Other declared types are
unaffected.
`with_connection`, `with_hostname` and `establish_connection` no longer
silently fallback to the default db. When we want that fallback
behavior, we have to specifically opt-in.
For example:
- Most hostname-based lookups in Discourse should fallback to the
default site, so that we match the existing behavior for any sites which
may have misconfigured hostnames
- Loops like CachedCounting, keep_readonly_mode and sidekiq-pausable
should defensively skip attempts to connect to sites which are now
missing, so that they don't get blocked by an exception for one missing
site
Depends on https://github.com/discourse/rails_multisite/pull/40
`Plugin::JsManager.maybe_cache` used `@cache.fetch(key, &blk)`, which
returns the block's value on a miss but never stores it, so the cache
never worked, which led to a large number of filesystem reads.
This commit fixes the cache, and also introduces
`CurrentAttributes`-based caching in development (to match what we do in
`lib/ember_assets.rb`)
`maybe_cache` is now `get_set_cache`, because it always reads through
and writes back.
concurrently 10 (bumped in #42829) stopped exporting the run function as
the module's top-level export, so `bin/dev` crashed on startup with
"concurrently is not a function". Use the named export.
Extracted from the Ember 7 PR (#40407). Vite injects the
`development`/`production` export condition automatically, but our
rolldown config did not.
This is a no-op on `main` today, but becomes relevant since Ember 7
makes use of these flags.
Extracted from the Ember 7 PR (#40407). Ember 7's <LinkTo> no longer
considers `@models` when `@current-when` is a string.
Sidebar category/tag links pass a static multi-route `current-when`
string plus `@models` to stay highlighted across filter routes while
narrowing to a single category/tag. Under Ember 7 it causes every link
sharing the route list to light up at once.
This commit implements the `currentWhen` logic we want in JS. On Ember 6
this reproduces what <LinkTo> already does, so behaviour is unchanged.
`Application#injectTestHelpers` sets up Ember's legacy global test
helpers (`find`, `click`, `visit`, `pauseTest`, etc.). We've been
linting against these globals for a long time, and the system will be
finally removed in Ember 7.
We maintain the `pauseTest` global via a custom shim.
Extracted from the Ember 7 PR (#40407).
The edit route called `.find()` directly on the result of
`store.findAll`, which is a LegacyArrayLikeObject. That triggered the
`discourse.legacy-array-like-object.proxied-array` deprecation and its
admin notice whenever an admin opened an embedding configuration. The
llms and agents edit routes already go through `.content`, so this
brings embeddings in line with them.
Also adds a system spec that edits an existing embedding configuration,
since none covered the edit page before. It fails on the old code
because the deprecation is fatal in system specs.
Selecting a light color scheme also previews it as the dark scheme, so
the test needs to clean up both the cs-preview-light and cs-preview-dark
links it creates. Leaving the dark one behind was leaking a stylesheet
into document.body for the rest of the QUnit run, which sometimes
tripped an unrelated boot-readiness check depending on test order.
Data Explorer used to splice report parameter values straight into the
saved SQL as escaped literals. When a report placed a parameter inside a
PostgreSQL dollar-quoted literal, a value could close that literal with
its own `$tag$` and append a second statement, because single-quote
escaping means nothing inside a dollar quote. A group member running
such a report through `POST /g/:group_name/reports/:id/run.json` could
use this to read any row the report's database role can see, such as
another user's email.
Values now reach PostgreSQL as bind parameters rather than as text.
`run_query` rewrites each `:name` marker that sits in real code to a
positional `$N` placeholder, collects the values, and passes them to
`async_exec_params`, so a value can never be parsed as SQL no matter
what it contains. This also closes the multi-statement path, since
`exec_params` refuses more than one command.
While the lexer is still regex-based, it is no longer part of the
security paradigm of data-explorer.
- A single lexer, `scan_sql_segments`, walks the SQL once and tells the
rewriter which `:name` markers are real code and which sit inside a
string, comment, or dollar-quoted literal that must be left alone.
- A parameter inside a dollar-quoted literal cannot be a bind, so it is
rejected with a clear error instead of being silently mishandled.
- A list value expands to a run of placeholders, so `IN (:ids)` keeps
working.
- The workflow raw SQL node runs through the same bind path via
`run_query_with_values`, and the old inline `interpolate_params` and its
escaping are gone.
From time-to-time, variables may need to be renamed in core. To avoid
breaking existing themes and plugins, this commit introduces a system to
automatically rewrite old variable names to new ones. A list of renames
is maintained in `stylesheets/variable-renames.json`, and is applied
when CSS is compiled. This list also powers a stylelint rule which will
automatically rewrite old names to new names in source code.
Transformations apply to all core/theme/plugin code. For now, the
stylelint rule/autofix applies to core only, but this will be extracted
to `@discourse/lint-configs` in the near future.
When a transformation is applied, it adds a trailing `/* automatically
renamed --old to --new */` comment so that the behavior is
understandable from the browser developer tools.
Mirrors site-setting support added in 802fa4c356.
Also updates the sprite-sheet logic so that it includes all site/theme
settings with `type: icon`, in addition to the existing logic which
looked for setting names ending in `_icon`.
Images in the review queue could render at their natural size and spill
out of the post content box, which made moderating a post with a large
image awkward. The topic stream caps images with a rule on `.cooked`,
but the review queue renders into `.review-item__post-content`, so
nothing applied there.
This is most visible on posts waiting for approval, since their content
is cooked straight from the raw text and never goes through the image
resizing that a published post gets.
- Cap images in `.review-item__post-content` at the width of the box and
let the height follow.
- Leave avatars and emoji alone so they keep their own sizing.
Previously, the blocks API used the backtrace (via `identifySource()`)
to find the owner of a block. This is relatively slow, and can be broken
by things like browser extensions or other theme/plugin scripts.
This commit updates PluginApi instances so that they accept a `source`,
and sets up the theme/plugin build system to automatically pass that
source. Theme and plugin authors continue importing the plugin-api and
api-initializer as normal, and a virtual module is used to intercept the
import and provide a wrapped version.
Alternative implementation to #41090
`EditCategoryPanel` rendered a `customComponent` resolved by name
through the resolver, but nothing has ever passed one since the hook
shipped in 2015. Plugin category tabs use `registerEditCategoryTab` with
component classes instead. `buildCategoryPanel` now extends `Component`
directly and the empty base class is gone.
The bulk-action modal's `setComponent` and sidebar section links'
`contentComponent` were already documented as taking component classes,
but rendering them through the `{{component}}` helper meant a string
would silently resolve through the resolver. Angle-bracket invocation
and `curryComponent` only accept classes, so string-based reliance can't
creep in.
The column is already bigint, but the sequence was still integer, so
would break if any site exceeded max_int.
Migration is intentionally timestamped slightly in the past, so that it
can be cleanly backported to `release/2026.7`. This migration is
completely standalone, so ordering is not a concern.
Provides a way for plugins to register reviewable components without
using magic string-based lookups. This will be compatible with future
work on core/plugin/theme JS bundle splitting.
Previously, lib/pretty_text.rb assembled the mini_racer context by
transpiling and loading ~50 JS modules one-by-one at boot. This commit
replaces that with a single Rolldown-built bundle, precompiled during
assets:precompile and cached on disk under a digest of its inputs. This
uses a new `PrecompiledBundle` class, which is extracted from
`AssetProcessor`.
The whole Ruby -> JS interface now goes through mini_racer's `call`,
which is significantly faster & safer than the old `.eval` strategy.
The plugin interface is maintained by registering modules in
`loader.js`. This is a similar compatibility strategy to the one
currently being used for frontend code.
Most site-setting-related data is cached in-process. The exception was
client_settings_json, which was cached in Redis. This could create some
surprising behaviors, especially during deploys while multiple versions
of the app are running against the same redis instance. It was somewhat
mitigated by keying on git_version, but this was not perfect (e.g. if
plugins change, or a patch is applied without committing).
This commit refactors things so that the client_settings and the
JSON-serialized copy are cached in-process along with the rest of the
site-settings data. This should be more robust, and also faster. It also
provides direct access to the client settings hash, before it's
serialized to JSON.
Previously, api.dicourse.org would track whether an update is 'critical'
or not, and then change the color/design of the 'update available'
indicator in the dashboard. These flags have not always been set
reliably, and the extra complexity is not worth maintaining. If an admin
wants to find out what's included in an update, we now have a clear
changelog linked from the dashboard.
We were only setting up the listener during the service initialization,
which meant that we missed any deprecations which were thrown before
that (e.g. hbs-extension).
This commit adds a new `registerUniversalDeprecationHandler` API which
supports replaying any deprecations which fired before the handler is
registered. Similar pattern to browser APIs like
`PerformanceObserver.observe(..., {buffered: true})`
Reverts #42059, which dropped the `ProblemCheck::Landlock` check. This
restores the check, its spec, and re-registers it in the problem-check
list. The `landlock` locale string was left in place by the drop, so no
locale change is needed.
- Make timeout configurable for OAuth2, to match OIDC
- Correctly log timeout errors
- Show nicer auth-failure screen to users, instead of the generic
'something went wrong' page
Compute releaseDate and supportEndDate as the last Tuesday of the
relevant month (a fully-specified YYYY-MM-DD) instead of an approximate
YYYY-MM month, and convert the remaining vague values in versions.json
to their precise last-Tuesday equivalents.
---------
Co-authored-by: Loïc Guitaut <loic@discourse.org>
We previously supported a subset of HTML in tag descriptions, but the
escaping/unescaping of characters wasn't perfectly consistent, so
results could be surprising.
This commit runs tag descriptions through our standard markdown
pipeline, which supports the same subset of HTML, plus real markdown. It
also adds the standard DEditor in tag editing forms. This has parity
with group/user bios, and with category descriptions.
Now the character support is clearly defined, and perfectly matches
other parts of Discourse.
---
<img width="527" height="545" alt="SCR-20260723-rdpm"
src="https://github.com/user-attachments/assets/812a9a3a-ece7-4594-8d9b-65e7e30649c8"
/>
---
<img width="564" height="213" alt="SCR-20260723-rduy"
src="https://github.com/user-attachments/assets/4ea4a6ff-81d3-4489-95b3-bf5f9b1e5fa0"
/>
---
There is already a `#data-preloaded` on the page, which was interacting
badly with the test. Add before/after logic to temporarily remove the
real preload element while these tests run, then restore it afterwards.
- Update all imagemagick calls to go through a new `::Imagemagick`
wrapper, which wraps the command in `Discourse::SafeExec`
- Patch image_optim to force its calls through `SafeExec`
This provides robust defense-in-depth against vulnerabilities in image
processing binaries. Landlock is supported on Linux Kernel 5.13 and
above.
- Bump landlock to 0.3
- Refactor `SafeExec` now that Landlock gem is always present (it's no
longer conditional in the Gemfile)
- Strip ENV even when landlock is unavailable (for more consistent
developer experience on macOS)
- Add problem check which alerts admins if a production instance is
running without landlock support
b1399d6a6f removed the last mobile/desktop-specific CSS from Discourse.
Everything is now handled by media queries in common scss.
This commit drops the mobile/desktop stylesheet infrastructure for core.
It remains in place for themes & plugins.
Much of the diff is updating specs to avoid mobile/desktop as fixtures,
and to remove argument defaults which no longer make sense.
Upcoming changes with `body_class: true` add a `uc-*` class to `<body>`.
This class is temporary, so we do not want core/theme/plugin CSS to
become dependent on it. Therefore we must not allow it to contribute
specificity to selectors.
This commit adds a `discourse/uc-classes-in-where` which reports any
misuse of `.uc-*` classes, fixes up some existing cases, and documents
the pattern in the upcoming-changes skill.
Previously, `image_caption_enabled_validator_spec` saved the
`ai_image_caption_agent` override with a string key via
`SiteSetting.provider.save`, so the symbol-keyed `remove_override!`
cleanup never cleared it and a rolled-back agent id leaked into later
specs in the same worker, making `generate_post_image_captions_spec`
fail with `agent_missing` whenever the two ran together.
This change saves the override with a symbol key so it round-trips with
the standard cleanup, keeping the setting isolated per example.
Adding the Zoom SDK, and its react-related peerdeps, had a noticable
impact on Discourse core's build time. It created the largest JS chunk,
which took more than 10s to brotli-compress on our build machine.
Instead, we can load the built versions of the dependencies from Zoom's
CDN. Normally we try to avoid this because of the extra runtime
dependency. However, given that showing a Zoom meeting is already
dependent on Zoom's servers, that isn't a concern in this case.
Upstream docs for CDN-based SDK loading can be found
[here](https://developers.zoom.us/docs/meeting-sdk/web/get-started/).
When precompiling stylesheets, we were forcibly recompiling, even if we
already had the correct stylesheet in the cache. This commit updates the
precompile job so that it tries to pull an already-compiled copy from
the database and cache it on disk, rather than doing a full recompile.
Ruby 3.4 shipped support for happy eyeballs in `Socket.tcp` and
`TCPSocket`. However, our `FinalDestination::HTTP` wrapper was
performing a DNS lookup and passing IP addresses one at a time when
opening the socket. That meant that we didn't benefit from the new Ruby
feature in most Discourse features.
This commit factors the strategy. Now, `FinalDestination::HTTP` encodes
the DNS result and passes it to the underlying implementation as a fake
hostname string. A patch to `Addrinfo` detects this fake hostname and
returns the given IPs instead of performing its own lookup.
For this Addrinfo patch to work, we also had to patch `TCPSocket` so
that it uses the ruby-based `Socket.tcp` rather than its native C
socket-opening code.
The result is that we now get the benefit of the native Ruby 'Happy
Eyeballs' support for concurrent ipv4 and ipv6 connections.
All this patching of low-level ruby classes is not ideal, but there is
no native way to control name resolution in `Net::HTTP` or its
dependencies.
We've been using basic type-checking via JSDoc for some time. This
commit allows us to author proper `.ts`/`.gts` files, and use the full
typescript syntax. Initially, only d-button and a single chat file are
migrated, as proof of functionality.
In future, we may migrate more files, and consider making our tsconfig
more strict.
- Convert from a classic to glimmer
- Refactor `_checkSize` observer to be a declarative `renderTimeline`
getter, leaning on `TrackedMediaQuery`
- Drop jQuery
- Refactor drag handling to `modifierFn`
- Replace `canRender` + `didUpdateAttrs` re-render hack with the
`{{#each` pattern
- Replace the two-way `expanded` binding into `topic-progress` with an
`@onExpandToggle` callback
- Fix a pre-existing bug: `onSwipeEnd`/`onSwipeCancel` called
`shouldCloseMenu`/`getMaxAnimationTimeMs` as instance methods, but they
are module-level exports, so swipe-to-dismiss was throwing an error
click-track was relying on `e.currentTarget`, which may not be the
actual link in a vanilla-js event. It worked in JQuery because listeners
could be set with filters like `"a"`, and `currentTarget` would be set
accordingly. But in vanilla JS, `currentTarget` is the wrapper element
with the event-listener attached.
Instead, we can use `event.target.closest("a")` to find the actual link
element.
Upgrades @glint/ember-tsc from 1.5 to 1.8.11 (along with @glint/template
1.7.8 and @glint/tsserver-plugin 2.5.17), which fixes extensionless
resolution of `.gjs`/`.gts` module imports under ember-tsc's `-b`
(solution build) mode used by `lint:types`. Previously extensionless
resolution only worked in single-project mode; the solution builder
bypassed Volar's resolution hook, so imports of `.gjs`/`.gts` modules
without an explicit extension failed with TS2307.
Upstream fixes:
https://github.com/typed-ember/glint/commit/f1b13305e5d49c44b1dc651d75f6d69ab3a69a03
and
https://github.com/typed-ember/glint/commit/b519399e0ef6aeab6ad65da97bdf16138f0f490e
With extensionless resolution now working, the `/** @type
{import("….gjs").default} */` hints placed above component imports to
force type resolution are redundant, so they are removed.
Also broadens d-otp's `onInput` parameter type from `InputEvent` to
`Event`, which glint 1.8 correctly requires for the `{{on "input" …}}`
binding: the handler must accept the generic event the modifier
provides.
We stopped using this UI in most topic lists as part of the raw-hbs ->
glimmer conversion, since it was seldom-used, and simple links are a
better UX.
However, the implementation remained, and it was still accessible in
some very specific places (e.g. user-activity/topics on mobile devices).
This commit strips out the implementation, so we have consistent
behavior across all topic lists.
This was verified by a/b testing a large number of scenarios against old
and new implementations, and confirming that they returned precisely
identical coordinates.
This was doing direct manipulation of ember-rendered DOM, which is
risky. This commit changes it to be a simple non-animated closure, which
is safer, and more in line with other Discourse UX.
Previously, claiming a topic in the review queue threw `Cannot read
properties of null (reading 'id')` in every `ReviewableItem` whose
reviewable had no associated topic (e.g. `ReviewableUser` or a queued
new topic), because `_updateClaimedBy` dereferenced
`this.reviewable.topic.id` on the `/reviewable_claimed` broadcast.
This change compares against the existing `topicId` getter, which safely
resolves the topic id and lets topicless reviewables ignore the
broadcast instead of erroring.
Our tsconfig is designed for development use only, and is not needed for
a successful asset-processor build.
In most cases, it didn't cause a problem. However, if someone does `rm
-rf` on a core plugin, then it causes core's tscconfig to become
invalid, which then breaks the asset-processor build.