* Migrate property field reads to request context
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Preserve nil property service request context behavior
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Use explicit session attributes system context
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Use non-nil property contexts for internal calls
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Thread request context through content flagging lookups
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Update content flagging helper tests for request context
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Thread request context through content flagging values
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Fix build: remove unused context imports left after request.CTX migration
Migrating PropertyFieldStore to request.CTX removed the last
context.Context usage from the store.go interface, leaving an unused
"context" import in store.go and in the generated retrylayer/timerlayer
files (regenerated via `make store-layers`, with layer_generators now
stripping the context import when it's unused). Also drop the same
now-unused import in localcachelayer/main_test.go.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Remove context import directly from store layer templates
Store no longer has any context.Context methods after the request.CTX
migration, so drop the hardcoded "context" import from the
retry/timer layer templates instead of stripping it at generation
time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Remove deprecated store.WithMaster/sqlstore.WithMaster helpers
request.CTX is now used everywhere, so the deprecated
context.Context-based WithMaster helpers and their wrapper in
sqlstore have no remaining callers; inline the logic into
RequestContextWithMaster instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix TestDoSetupSessionAttributesProperties nil-context panic
Two subtests still constructed the pre-seed field state via
UpdatePropertyFields(nil, ...), which was the old system-caller
sentinel. isSystemCaller now requires an explicit SystemCallerContext
marker, so a bare nil rctx falls through into validateUpdate and
panics in RequestContextWithMaster. Use SystemCallerContext(th.Context)
like the rest of the suite already does.
* ci trigger
* ci trigger
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* [MM-70290] Run app migrations locked to the master DB
App migrations write rows and then read them back within the same
function. Those read-backs resolve to GetReplica(), so on a licensed
server with read replicas configured a replica that has not yet caught
up returns zero rows and doAppMigrations aborts startup via mlog.Fatal.
Reported after a 10.11.12 -> 11.7.8 upgrade on Aurora PostgreSQL with a
reader endpoint, crashing on the Managed Category Properties Setup
migration. Restarting is not a reliable workaround: the done flag is
written before the failing read, so the short-circuit path re-runs the
same replica read and a node can crash-loop while lag persists.
Wrapping doAppMigrations in LockToMaster/UnlockFromMaster covers every
read in both migration loops, including SqlPropertyGroupStore.Get and
SearchPropertyFields, which take no context and so cannot be fixed by
per-call-site routing. This mirrors the existing bulk import fix in
app/import.go, which locks to master for the same reason while the
server is serving live traffic.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Expect LockToMaster in the setup-functions store mock
doAppMigrations now locks the store to master, and it runs from
NewServer, so every helper that builds a server on the mock store hit
an unexpected-call panic. Registering both calls in
GetMockStoreForSetupFunctions covers the app, app/email, app/platform
and api4 helpers, which all share this mock.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-69962: Fix inline media flickering between sizes near the 480px container threshold
* bring back row range
---------
Co-authored-by: Nevyana Angelova <nevyangelova@Nevy-Macbook-16-2025.local>
* docs: restore generated plugin SDK reference pages
Reimplements the Hugo-era plugingodocs/pluginjsdocs/pluginmanifestdocs
shortcode pipeline natively in Docusaurus, so the server plugin SDK,
web app plugin SDK, and manifest reference pages render full generated
content again instead of "Generated content (migrating)" placeholders.
Two new Go generators (gen-plugin-godocs, gen-plugin-manifest-docs) and
one Node generator (gen-plugin-jsdocs.mjs) read server/public/plugin,
server/public/model, and webapp/channels/src/plugins/registry.ts
directly from this monorepo and emit gitignored JSON consumed by new
PluginGoDocs/PluginGoExample/PluginJsDocs/PluginManifestDocs React
components, wired into prestart/prebuild alongside the existing
sidebar/OpenAPI generators.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: remove migration plan doc from this PR
Was a handoff/status doc for the implementing agent, not meant to ship
as part of the change itself.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: make plugin-godocs/manifest-docs builds atomic
build:plugin-godocs and build:plugin-manifest-docs redirected straight
into data/plugin-*.json, which doesn't exist on a clean checkout (the
redirection itself would fail before the generator ever ran) and, on a
subsequent failed run, would truncate a previously-good JSON file
before failing. Create data/ up front, write to a .tmp file, and only
mv it into place once the generator exits successfully.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: fix review findings in plugin doc generators
gen-plugin-manifest-docs:
- jsonFieldName now excludes unexported fields (matching encoding/json)
and falls back to the Go field name when there's no tag or the tag's
name component is empty (e.g. json:",omitempty"), instead of
dropping the field entirely.
- exprTypeDocs's SelectorExpr case now resolves a qualified type only
when its package qualifier actually points at the model package
(checked against the declaring file's imports), instead of matching
any x.Sel identifier by name alone regardless of which package it
qualifies.
gen-plugin-godocs:
- The example-code loop now falls back to example.Code when
example.Play is nil (go/doc leaves Play nil when it can't synthesize
a whole runnable program), and propagates printer.Fprint errors
instead of discarding them.
No output change for the current server/public/plugin or
server/public/model content — verified via a full regen.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: drop legacy-repo comment references, align jsdocs generator parser
Remove "port of the old mattermost-developer-documentation Hugo
shortcode" comments from the plugin doc generators/components now that
they're fully native to this monorepo, and switch gen-plugin-jsdocs.mjs
from the TypeScript compiler API to @typescript-eslint/typescript-estree
to match the old script's parser/shape, while keeping the more robust
reArg-declared-parameter-name and node-scoped comment-attachment logic.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: bump plugin doc generators' go.mod to go 1.26
go 1.23 was arbitrarily low; align with a current Go version. go run
auto-downloads a matching toolchain on older local installs, so this
doesn't reintroduce a dependency on server/public/go.mod's version.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: handle RestElement in gen-plugin-jsdocs parameter extraction
paramNamesFromPattern silently dropped rest parameters/destructured rest
members (e.g. (a, ...rest) or {a, ...rest}), since RestElement matched
none of its type checks. No effect on today's registry.ts output (no
rest patterns currently used there), but keeps the extraction correct if
one is ever introduced.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: simplify plugin doc generators README note
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: check stdout write errors in plugin doc generators
os.Stdout.Write's return errors were discarded, so a failed/partial
write (e.g. broken pipe, disk full) would still exit 0. Combined with
the npm scripts' > file.tmp && mv pattern, a truncated write could be
treated as a successful generation. Fatal on either write failing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: flatten Server/Webapp plugin SDK reference sidebar nesting
server/server-reference.md and webapp/webapp-reference.md were the only
file in their respective folders, so the sidebar generator (which only
collapses a folder into a single entry when it has an index.md) rendered
them as a "Server" > "Server plugin SDK reference" category with one
child instead of one flat entry, unlike every sibling reference folder
(rest-api/index.md, bot-accounts/index.md, etc.).
Rename both to index.md to match that convention, and update the ~20
cross-referencing links (many with #anchor fragments) that pointed at
the old /reference/server/server-reference and /reference/webapp/webapp-
reference paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix Avatar layout regressions from MM-69802 without font-size:0
Broken images are no longer replaced elements, so width/height stop
applying and alt text sizes the box. Use inline-block plus overflow and
transparent color so the existing size tokens still apply and alt text
cannot spill out of the circle.
This restores em margins (scheduled/drafts DM avatars) and flex min-width
(agent selector, post previews), and removes the Content Flagging
preview spot-patch from #37972.
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
* Trigger Enterprise CI after mostest_password fix
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* Bump Go version to v1.26.7
* Fix digest
* Update glibc to v16 in runtime image as well
* Trigger E2E tests
* MM-70307: Fix FIPS E2E test failure due to short PostgreSQL password
OpenSSL FIPS requires HMAC keys to be at least 14 bytes (112 bits). The
password 'mostest' (6 bytes) triggers a panic in lib/pq's SCRAM-SHA-256
authentication when the server runs under go-msft-fips with the
glibc-openssl-fips:16 image. Replace it with 'mostest_password' (16
bytes) in all E2E test PostgreSQL connection strings and container
configs. LDAP admin passwords are unaffected (different protocol).
* Revert "MM-70307: Fix FIPS E2E test failure due to short PostgreSQL password"
This reverts commit d31b0eb5e8.
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
* Change test password to mostest_password
This makes the password compliant with the 112 bits minimum length
requirement. Otherwise, FIPS-compliant OpenSSL implementations will
panic when trying to connect from `lib/pq` with a shorter password.
* Simplify test templates' POSTGRES_PASSWORD values
* make generated
* Modify missing "mostest" strings
* MM-69323 Enable Concurrent React
* Fix Cypress tests involving post dot menu
* Fix flaky Cypress tests involving Team Settings modal
* Fix setState in permissions code when called twice in rapid succession by tests
* Update fullname_spec.js to wait for suggestion list to close
This is needed because the setState in SuggestionBox.clear isn't processed until
after the Enter keypress registers. Alternatively, we could wrap that in flushSync,
but since this seems to only occur during testing, I decided not to change
the web app code.
* Update more E2E tests
* Fix accidental commented test code
* Switch useContainerDimensions to useLayoutEffect to fix newly introduced layout shift
* Skip post_height SVG test on all browsers and message attachment test on Firefox
* Bump changes to feature flag for a future PR
* Enable concurrent React in E2E tests
* [MM-69866] Add Applies-to resource picker (Users, Channels, Posts) to New attribute
Adds an "Applies to" Card to the Global Attributes "New attribute" page,
letting a sysadmin pick which resources (Users, Channels, Posts) an
attribute applies to before saving. Each selected resource becomes its
own linked PropertyField, created serially after the template so a
partial failure can be attributed to a specific resource and rolled
back deterministically (linked fields deleted before the template,
per the server's deletion-order protection).
A `user`-scoped linked field shares its namespace with Custom Profile
Attributes, so name conflicts and the shared 20-field cap are newly
reachable failure modes -- both get distinct, actionable banners
instead of a generic failure message.
Client-only change; no server, migration, or Client4 changes required.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Split the Applies-to row into one component per resource type
Replaces the single resourceType-parameterized AttributeAppliesToItem
with three dedicated components (User/Channel/Post), each hardcoding
its own icon, label, and testids instead of looking them up by a prop.
All three share one prop signature (AttributeAppliesToItemProps,
exported from attribute_applies_to_constants.tsx) so the parent's
Record<ResourceObjectType, ComponentType<...>> lookup map fails to
compile if any of the three drift from it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix Prettier formatting in the global_attributes E2E files
npm run check for e2e-tests/playwright runs lint && prettier && tsc,
and CI's prettier --check step was failing on both files -- they'd
only been checked against webapp's ESLint config locally, not this
package's Prettier config.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Move the Applies-to row's Remove action into the expanded state
The collapsed row no longer has any remove affordance -- the only way
to remove a resource is to expand it first, then click "Remove
resource" in the header (still a sibling of the toggle, not nested
inside its clickable area). Styled per the design prototype: a plain
text button, transparent by default, --error-text colored, with a
rgba(--error-text-color-rgb, 0.08) hover tint -- the same pattern
already used by OrphanedFieldDeleteButton (system_properties) rather
than a new one-off style.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add 12px right margin to the Applies-to row's Remove button
Matches the design prototype's spacing between the button and the
row's right edge.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Rename applies-to constants file to .ts
The file has no JSX, so the .tsx extension was misleading.
* Polish the Applies-to card empty state and expanded rows
Match the design: updated copy and type, primary/tertiary add buttons, header-only open tint, and a 176px form row in the expanded body.
* Drop bottom padding on the last Applies-to body row
It stacked with the container padding and looked doubled.
* Size Unique name label, value, and Edit to 12px
The caption and Edit link were 14px against a 12px label; shrink the edit input to match.
* Treat Unique name click-away as Done
Opening Edit then clicking away left the input open and froze auto-derivation. Blur now uses the same commit path as Done, matching the channel URL field.
* Lock Type to Text while an external source is linked
LDAP and SAML only sync on text fields; disabling the type menu until the last chip is removed keeps that invariant in the UI.
* Show linked sources on the Options line as Synced with chips
Once a source is selected the chips replace the Text help copy; the divider stays until then so the unlinked state still separates Options from the add-source trigger.
* Order flex container properties to satisfy stylelint.
* Show CPA banners only for Users and copy leftover-template rollback.
Channels and Posts name conflicts used User Attribute wording, and a leftover template after a linked-field rollback looked like a clean save. E2E cleanup now rediscovers template and linked fields instead of relying on the success-path list.
* Use Compass Button for Applies-to row chrome.
Override quaternary styling so the accordion header stays body-text chrome, and keep Remove as tertiary destructive.
* Await hanging creates before unmount-save negative asserts.
waitFor(() => Promise.resolve()) returns on the first check and can pass before finalizeSave, so the mount guard was not actually load-bearing.
* Drop unused PropertyField and ResourceObjectType imports from the Playwright spec.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-70071: Automatically select hosted push notification server based on license
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* test: fix mock-store fallout from push endpoint license listener
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-70071: address review feedback on push endpoint sync
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retrigger enterprise tests against updated companion branch
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* ci: retrigger flaky artifact build
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-70071: revert any hosted push endpoint to test on entitlement loss
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-70071: add nil-safe License.HasMHPNS entitlement check
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
* MM-70071: drop preview-tree docs for auto-selected push server
Monorepo MDX is still unpublished; this belongs in mattermost/docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* MM-70071: stub InitEmailBatching on guest-invite email mocks
License teardown now SaveConfigs the push endpoint, which fires the existing email-batching config listener.
Co-authored-by: Cursor <cursoragent@cursor.com>
* MM-70071: don't re-init email batching on push-server license sync
License teardown SaveConfigs the push endpoint, which fired the existing
email-batching listener and panicked tests that mock EmailService.
Re-init batching only when EnableEmailBatching changes, and stub the
remaining invite mock used during helper cleanup.
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
* MM-70071: re-init email batching when the interval setting changes
Keep EmailBatchingInterval live at runtime; only ignore unrelated
config writes such as the push-server license sync.
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>
* MM-70071: isolate mock tests from push endpoint sync
Use custom push endpoints in shared mock fixtures so unrelated tests do not need config-listener expectations.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* [MM-70198] Overlay permalink preview "Show more" to avoid layout shift
Post permalink previews clip tall content and reveal a "Show more"
affordance only after the body is measured on mount. The ellipsis-style
button was rendered in normal flow below the clipped text, so when it
flashed in the preview grew taller and pushed following content down.
Position the ellipsis "Show more" button absolutely over the faded bottom
of the preview so it is revealed without changing the preview's height.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70198] Satisfy stylelint property order
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Change fade out to use a mask-image so colours match
* Change new post preview fade out to fully fade before toggle text
* Add E2E test for post preview Show more layout shift
Verify that the overflowing permalink preview 'Show more' control is
overlaid on the preview and does not change the post height when it is
revealed after mount.
* Move new E2E test to match format of others
* Address PR feedback: extend permalink preview fade transparent stop to 24px
matthewbirtch suggested moving the mask-image fully-transparent stop from
calc(100% - 18px) to calc(100% - 24px). Applied to both the -webkit-mask-image
and mask-image declarations to keep them consistent.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
* Improve plugin upload dropzone UX
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Refine plugin upload dropzone presentation
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Update plugin upload Cypress specs
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Preserve plugin upload drag state over children
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
* Show plugin manifest name in upload success message
The success toast previously interpolated the raw uploaded filename
(e.g. mattermost-plugin-fl3xx-v0.8.1-linux-amd64.tar.gz), which is not
meaningful to admins. Use the plugin manifest name returned by the
upload response instead, falling back to the filename if unavailable.
* Indicate upgrade/downgrade/same-version on plugin overwrite
State whether an overwritten plugin's success message represents an
upgrade, downgrade, or same-version replace, comparing the previously
installed manifest version with the newly uploaded one via semver.
* Refine plugin upload dropzone presentation and uploading state.
Use a native button with Compass upload icon, keep progress inside the dropzone without changing height, and reserve button color for hover/drag-active.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Show selected filename in the dropzone and improve upload status messaging.
Keep success/error feedback aligned with the dropzone, with a green check or red alert icon beside the message.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix no-nested-ternary lint error in plugin upload dropzone
Extract the upload dropzone title text into a helper method to
satisfy eslint's no-nested-ternary rule.
* Fix stylelint order/properties-order errors in plugin_management.scss
* Add unit tests for formatUploadOverwriteMessage semver paths
Covers the upgrade, downgrade, same-version, and fallback
(missing/invalid semver) branches, which the automated test-analysis
bot flagged as untested.
* Add unit tests for remaining disabled-reason branches and file drop
Covers renderUploadDisabledReason's isDisabled and !enable branches,
and adds a fireEvent.drop test for handleUploadDrop, closing out the
remaining gaps from the automated test-analysis review.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* [MM-57808][MM-57809][MM-57811] Graduate theme settings to Site Configuration > Customization
Move ThemeSettings.EnableThemeSelection, ThemeSettings.AllowCustomThemes, and
ThemeSettings.DefaultTheme out of System Console > Experimental > Features and
into Site Configuration > Customization, alongside the other branding settings.
The config keys, defaults, license gating, and the AllowCustomThemes dependency
on EnableThemeSelection are unchanged; only the System Console page and the
access classification move. The access tags become site_customization, so the
settings are now governed by sysconsole_{read,write}_site_customization rather
than sysconsole_{read,write}_experimental_features.
i18n ids move from admin.experimental.* to the Customization page's
admin.customization.* convention, and the documentation entries move from the
experimental configuration settings page to the site configuration settings
page.
* [MM-57812][MM-57813] Graduate tutorial and onboarding settings to Site Configuration > Customization
Move ServiceSettings.EnableTutorial and ServiceSettings.EnableOnboardingFlow out
of System Console > Experimental > Features and into Site Configuration >
Customization, next to the desktop app landing page setting that also governs a
user's first-run experience.
The config keys and defaults are unchanged; only the System Console page and the
access classification move. The access tags become site_customization, so the
settings are now governed by sysconsole_{read,write}_site_customization rather
than sysconsole_{read,write}_experimental_features.
i18n ids move from admin.experimental.* to the Customization page's
admin.customization.* convention, and the documentation entries move from the
experimental configuration settings page to the site configuration settings
page.
* [MM-57810] Surface ThemeSettings.AllowedThemes in Site Configuration > Customization
ThemeSettings.AllowedThemes has always existed in the server config and has
always been honoured by the theme picker, but it was never represented in the
System Console schema — not under Experimental > Features and not anywhere else.
Add it to Site Configuration > Customization alongside the theme settings it
constrains.
The server model is []string, so this uses the existing `type: 'text'` with
`multiple: true` widget, the same one ServiceSettings.DCRRedirectURIAllowlist
uses for its []string. The admin console joins the array with commas for display
and splits it back into an array on save, which matches how the client config
already serialises the value, so no behaviour or serialisation changes. The
setting picks up an access tag of site_customization, where it previously had
none, and the documentation entry moves from the self-hosted-only section of the
experimental configuration settings page to the site configuration settings page.
Also add a test asserting the graduated settings are present on Customization,
absent from Experimental > Features, and that AllowedThemes round-trips as a
string array.
* Add runtime-effect tests for graduated theme and onboarding settings
Expand coverage for the graduated Customization settings so that they are
verified to affect their features, not just to save:
- ThemeSettings.AllowedThemes: premade theme chooser only renders the
allow-listed themes (theme enforcement on/off).
- ServiceSettings.EnableOnboardingFlow: onboarding task list is gated on
the config value.
- ServiceSettings.EnableTutorial: the Channels tour tip is gated on the
config value.
- EnableTutorial/EnableOnboardingFlow (and the theme bools) round-trip
their value through the admin console schema.
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
* [MM-57806] Graduate Enable Channel Viewed WebSocket Messages
Move ServiceSettings.EnableChannelViewedMessages out of System Console >
Experimental > Features and into Environment > Web Server, alongside the
other ServiceSettings transport and performance knobs it belongs with.
The access tag changes from experimental_features to environment_web_server,
so the setting is now governed by sysconsole_read/write_environment_web_server.
write_restrictable and cloud_restrictable are preserved, the default remains
true, and the client config continues to publish the value. No runtime
behavior changes.
The i18n ids move from the admin.experimental.* namespace to the
admin.service.* namespace used by the rest of the Web Server page.
* [MM-57806] Document channel viewed WebSocket messages under Environment
Move the Enable Channel Viewed WebSocket Messages entry from the
experimental configuration settings page to the Web Server section of the
environment configuration settings page, matching its new System Console
location, and reformat it to the two-column table style used there.
* [MM-57806] Cover the new location of the channel viewed setting
Assert that the setting is defined on the Environment > Web Server page,
that it is gated on write access to the Web Server console resource rather
than to Experimental Features, and that searching the admin console for
"channel_viewed" resolves to environment/web_server.
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Update RB to latest version of fork
* Update most snapshots to remove extraneous div
* Forward ref to menu passed to RB Dropdown
* Update unusual tests broken by RB update
* Patch react-overlays to still close modal when default prevented
The old version of react-overlays that we had ignored this, but the newer version
causes the modal to not close when preventDefault is called on the escape keyboard
event. React Select always does that and some other components like the SuggestionBox
sometimes prevent default, and it doesn't seem like Floating UI or MUI look at
preventDefault to know whether or not to close the modal, so I think React Overlays
is the correct place for this patch.
* Remove @types/react-overlays in favour of built-in definitions
* Update RB to merged commit
* WIP:
* WIP:
* Added API integration
* Removed some unneeded functions and cleaned up unnecessery comments
* CI
* Test improvements
* Coderabit fixes
* Report data updates
* Handled commas and few other chaaracters in channel name
* Data spillage exposure radius UI integration (#37820)
* UI implementation and integration of exposure report APIs
* Minor cleanup
* Coderabit fixes
* Allowed generating exposure report irrespective of status
* Used the new button component
* fixed lint error
* ci: bump test-system-io-summary action for missed-spec status
Placeholder bump pending merge of mattermost-test-system-io summary fix.
Co-authored-by: saturnino <saturnino@mattermost.com>
* ci: re-pin test-system-io-summary to main e2d5032
Replace the pre-merge placeholder SHA with the latest
mattermost-test-system-io main commit, which includes the
squash-merged missed-spec summary fix from #96.
Co-authored-by: saturnino <saturnino@mattermost.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: saturnino <saturnino@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* [MM-70252] Reject malformed date filters in logs query API
The POST /api/v4/logs/query endpoint parsed date_from/date_to with a fixed
layout and swallowed parse errors, silently dropping the bound instead of
signalling the caller. A malformed date_from became the zero time and a
malformed date_to became now, so the request returned HTTP 200 with an
unfiltered result set.
Add LogFilter.IsValid, which rejects a non-empty bound that cannot be parsed
with the shared LogFilterDateLayout while keeping empty strings meaning
"unbounded", and call it from queryLogs so a bad filter returns 400 naming the
offending field and the expected layout.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70252] Add tests for logs query date filter validation
Add a unit test for LogFilter.IsValid covering empty (unbounded), valid, and
malformed bounds, and an api4 integration test that drives POST /logs/query
through the real router to assert malformed date_from/date_to return 400 with
the offending field id while empty and valid bounds return 200.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70252] Harden logs query date filter tests
Address test-quality review: exercise the DateTo validation branch with a valid
non-empty DateFrom, move fallible checks out of the require.Eventually condition
to avoid a cross-goroutine failure, and make each api4 subtest self-contained by
polling for the expected messages via a shared helper so valid-bounds also
verifies filtering still returns records.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70252] Retrigger CI/CodeRabbit after invalid public-module feedback
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70252] Note shared LogFilterDateLayout usage in date filter
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70252] Add Client4.QueryLogs to simplify logs query date filter tests
* Address PR feedback: 2 answered, 1 resolved, 0 declined
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
* introduce mlogFieldNaming
* apply vet-fix changes
* Cover every keyed mlog constructor in the analyzer fixture
* clarify end result in comment
* Check mlog field keys on explicitly instantiated constructors
* [MM-57557] Remove dead Email login button color settings from the server
EmailSettings.LoginButtonColor, LoginButtonBorderColor and LoginButtonTextColor
were plumbed into the client config as EmailLoginButtonColor /
EmailLoginButtonBorderColor / EmailLoginButtonTextColor, but no client — web or
mobile — ever consumed them, so the email login button was never colored by
these values.
Remove the fields from the config struct and its defaults, drop the three client
config props, and update the config fixtures that carried them.
Also fixes MM-57556 and MM-57804, and follows the same removal already done for
the AD/LDAP (MM-70140) and SAML (MM-70141) equivalents.
* [MM-57557] Remove Email login button colors from the webapp and docs
Drop the three Email Login Button Color settings from the Admin Console
Experimental Features section along with their en.json strings, remove the
matching ClientConfig and AdminConfig EmailSettings entries to stay in sync with
the server model, and delete the corresponding documentation entries.
The experimental settings doc's jq example referenced
EmailSettings.LoginButtonColor, which no longer exists; point it at
EmailSettings.EmailBatchingBufferSize instead.
* [MM-57814][MM-57815] Graduate user typing settings to Site Configuration > Posts
Move ServiceSettings.EnableUserTypingMessages and
ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds out of
System Console > Experimental > Features into the Performance & Limits
section of System Console > Site Configuration > Posts, and reclassify
their access tags from experimental_features to site_posts (preserving
write_restrictable and cloud_restrictable).
The two settings stay adjacent, and the timeout remains disabled while
typing messages are off. The timeout label now states its unit, since
"User Typing Timeout" alone did not convey milliseconds. The i18n ids
move from admin.experimental.* to the Posts page's admin.posts.*
convention; the "E.g.: 5000" placeholder previously shared with the
experimental user status and profile fetching poll interval is now
defined once per setting.
No config keys, defaults, or runtime behavior change.
* [MM-57814][MM-57815] Assert user typing settings are searchable under Posts
Searching the System Console for "typing" now also matches
Site Configuration > Posts, guarding the new location of the user typing
settings. Experimental Features still matches on unrelated help text
about typing a tilde to trigger channel autocomplete.
* [MM-57814][MM-57815] Move user typing settings docs out of Experimental
Document "Enable user typing messages" and "User typing timeout" in the
Posts section of the site configuration settings guide, and drop them
from the experimental configuration settings guide.
* [MM-69865] Enable the Delete row action on Manage Attributes
The kebab menu's Delete was stubbed as disabled + "Coming soon". Wire it
to a confirmation modal and the existing DELETE property-field endpoint,
dispatching PROPERTY_FIELD_DELETED so the row leaves via Redux rather
than local component state.
The modal closes immediately on confirm instead of freezing behind a
spinner, so failures surface in an AlertBanner above the table rather
than inside a modal that is already gone. A 409 (the server refuses
while live linked dependents exist) gets its own message instead of the
generic one -- the wording says "other attributes are still linked to
it", matching what CountLinkedFields actually counts, not the AD/LDAP
and SAML source links, which are plain attrs on the field itself.
Delete stays disabled on plugin-owned rows, relabelled "Plugin-managed"
so the reason is visible rather than the item silently doing nothing.
E2E covers confirm, cancel, and the 409 branch against a real server
response: the linked dependent is seeded through the API, which needs a
non-template object type because IsValid rejects a template field
carrying a linked_field_id.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Resolve server-only plugin names instead of showing the raw plugin ID
getPluginDisplayName only read state.plugins.plugins, which holds
manifests for plugins that shipped a webapp bundle and registered
themselves in the browser. A server-only plugin is never in that map, so
every caller fell through to the fallback and rendered the bare ID --
"com.mattermost.gahelper" in the Manage Attributes Source column.
Consult state.entities.admin.pluginStatuses as a second source before
giving up on the ID. That map covers every installed plugin, server-only
included, and is empty for non-admins, so the non-admin callers
(user_settings_general, integrations/bots) are unaffected.
Nothing on the Manage Attributes page loaded those statuses, so fetch
them there -- once, and only when a plugin-owned row is actually
present. The result is deliberately not awaited: a failure just leaves
the column showing the ID it was already showing.
Also fixes the same latent fallback on custom_profile_attributes,
user_properties_values, and system_user_detail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep e2e attribute names under the 40-char Unique name cap
Every test in the "create attribute" block built expectedName from a
long prefix plus a 13-digit Date.now(), overshooting the Unique name
input's Constants.MAX_CUSTOM_ATTRIBUTE_NAME_LENGTH (40). The derived
slug truncated silently, so "playwright_created_attribute_<13 digits>"
(42 chars) was asserted against the 40 chars actually rendered.
The two linked-source tests only use expectedName for cleanup, so
instead of failing they quietly leaked their seeded field onto the
shared server on every run.
Shorten the prefixes so the derived slug fits. The whole file is
mode: 'serial', so these failures also skipped every test after them --
including the delete coverage at the end of the file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Announce the delete error banner to assistive tech
The banner was mounted together with its message, and an alert inserted
into the DOM at the same moment as its text is not reliably announced --
so a screen-reader user got silence when a delete they had just
confirmed failed.
Keep the region mounted and swap only its content, matching the reason
attribute_external_source.tsx already keeps its own status region
mounted rather than rendering it alongside the announcement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* [MM-69865] Allow deleting a plugin-owned attribute once its plugin is uninstalled
Delete was permanently disabled on plugin-owned rows, but the server only
protects such a field while its source plugin is still installed:
checkFieldDeleteAccess allows the delete once the plugin is gone, which is how
an admin cleans up what an uninstalled plugin left behind.
Reuse the User Attributes page's orphan check rather than growing a second one.
isFieldOrphaned moves out of system_properties/orphaned_fields_utils.ts into
utils/properties.ts, widened to PropertyField so both pages can use it, with the
redux-aware hook in components/common/hooks/use_field_orphaned.ts. The hook
unions admin.plugins and admin.pluginStatuses because the two attribute pages
populate different slices, and a field should not read as orphaned merely
because the page rendering it loaded only one of them.
The delete confirmation now names the uninstalled plugin, since an admin has no
other way to tell where the leftover attribute came from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix the delete-error scroll and the premature orphan check
The error banner sits above the table, so a delete confirmed from a row
further down left the failure off-screen. The existing scroll fired on the
error state, which lands during the modal's fade-out: GenericModal passes
restoreFocus, so react-bootstrap returns focus to the row's actions button
on close, and focusing an off-screen element scrolls it back into view --
undoing the scroll every time.
GenericModal also starts closing before it invokes handleConfirm, so the
delete response can land either side of the fade and the error and the exit
arrive in either order. Scroll only once both have landed, take focus on the
banner without its own scroll, then scroll the console wrapper to the top.
Focusing the banner also leaves keyboard and screen reader users at the
error rather than back on a row button.
Separately, gate the orphan check on the plugin inventory having settled.
Both admin.plugins and admin.pluginStatuses start empty, and the fetch is
dispatched from an effect, so an inventory that has not loaded is
indistinguishable from one where nothing is installed -- which isFieldOrphaned
reads as "every plugin-owned field is orphaned". A protected row briefly
offered Delete behind a dialog wrongly claiming its plugin was uninstalled,
which the server would then refuse anyway. Settled rather than resolved: a
failed fetch still leaves the inventory as good as it will get, and staying
false forever would strand genuine leftovers as undeletable.
The same gap exists for the system_properties consumers of the hook, which
never fetch the inventory at all; left alone here and noted on the hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Omit sanitized channel member timestamps from JSON
The channel member sanitization introduced in #33835 replaced other
users' LastViewedAt and LastUpdateAt with -1, which clients decode as
Dec 31 1969. Serialize the sanitized sentinel as an absent field instead
so the API no longer returns an invalid timestamp for other users.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Add tests and API docs for omitted sanitized member timestamps
Verify at the JSON layer that last_viewed_at and last_update_at are
omitted for other users' memberships (across the channel and user
endpoints) while remaining present for the requester, including a
legitimate zero timestamp. Document the omission in the API spec.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Strengthen sanitized-timestamp test coverage
Cover the NDJSON streaming branch of getChannelMembersForUser and the
getChannelMembersForTeamForUser endpoint, assert the requester's own
timestamps are valid (not the sentinel), use the sanitizedTimestamp
constant, and note the ChannelMemberForExport marshaling footgun.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Marshal team data via a typed struct in ChannelMemberWithTeamData
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Avoid shadowing err in ChannelMemberWithTeamData.MarshalJSON
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Use omitzero tags to omit sanitized member timestamps
Replace the custom ChannelMember/ChannelMemberWithTeamData MarshalJSON
round-trip with the Go 1.24 omitzero tag on LastViewedAt/LastUpdateAt.
SanitizeForCurrentUser now zeroes another user's timestamps so they are
omitted from API responses, per reviewer feedback.
* Give current user a real last_viewed_at in sanitization test
With omitzero, a zero last_viewed_at is legitimately omitted. Have user2
post an unread message and the current user view the channel so the
current-user assertions verify a genuine timestamp survives sanitization.
* Use -1 sentinel for sanitized member timestamps with single-pass marshal
A last_viewed_at of 0 legitimately means "never viewed", so it cannot
double as the sanitization sentinel. Restore the -1 sentinel and omit it
during serialization via shadowing pointer fields, avoiding the previous
marshal/unmarshal/marshal round-trip.
* Clarify ChannelMember.MarshalJSON doc comment per review feedback
* Address PR feedback: 0 answered, 4 resolved, 0 declined
- Simplify sanitizedTimestamp and SanitizeForCurrentUser doc comments per review
- Document that new ChannelMemberWithTeamData fields must be added to MarshalJSON
- Add round-trip test guarding against fields dropped by MarshalJSON
* Address PR feedback: remove round-trip MarshalJSON test
The round-trip test did not guard against forgetting to add a new field to
MarshalJSON, since the same field would also be missing from the test.
* Address PR feedback: assert legitimate zero last_update_at is serialized
* Mark sanitized channel member timestamp fields as nullable in OpenAPI spec
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* [MM-69646] Disallow MoveThreadsEnabled feature flag
Reject the MoveThreadsEnabled feature flag during config validation so the
server fails to start while it is enabled. The feature is being retired in
favor of Wrangler and will be removed later.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-69646] Cover nil FeatureFlags guard in config validation test
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Move MoveThreadsEnabled comment into isValid method body
Keep isValid's doc comment generic since it will validate more flag
combinations in the future, and place the MoveThreadsEnabled-specific
rationale next to the actual flag check.
* [MM-69646] Update TestMoveThread for retired MoveThreadsEnabled flag
Config.IsValid now rejects enabling MoveThreadsEnabled, so the
move-thread API stays disabled. Replace the enabled-path suite with
assertions that the flag cannot be turned on and MoveThread returns 501.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-69646] Stop forcing MoveThreadsEnabled in e2e environments
E2E was setting MM_FEATUREFLAGS_MOVETHREADSENABLED=true, which now fails
Config.IsValid and prevents the test server from starting. Remove the
override and skip Cypress move-thread specs that require the retired flag.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-69646] Skip TestMoveThread instead of asserting disabled flag
Mirror the E2E describe.skip approach: retain the original TestMoveThread
body and skip it at the top, since MoveThreadsEnabled is retired and
rejected by Config.IsValid.
* [MM-69646] Park cursor away from post dot menu in edit_file_attachment specs
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
The webapp Slack import path was deprecated in v6.0 in favor of mmetl
and Mattermost bulk import; remove the leftover API, importer package,
CLI command, and import_team permission.
Co-authored-by: Cursor <cursoragent@cursor.com>
MediaGallery hid its header for single tiles, so a collapsed video
disappeared with no way to expand it.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Add delegated administration roles to Manage Roles modal
Allow admins to grant custom (delegated granular administration) roles
directly from the System Console > Users > Manage Roles modal, instead of
only via Delegated Granular Administration.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Add tests for delegated administration roles in Manage Roles modal
Cover rendering, pre-checking, availability filtering, bot handling,
merge/remove on save, ordering, admin/member toggling, save errors,
modal close, and user-switch state reset.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Fix lint in manage roles modal test
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Address PR feedback: link to system roles page and refine delegated roles spacing
- Replace per-role subtext with a single help-text link to the System Roles
page, since customized roles can differ from the default descriptions
- Add padding below the radio buttons above the divider
- Left-align the delegated roles section with the section above and add
padding below the divider
* Address PR feedback: license-gate delegated roles, refine help text, dedupe SCSS
* Assert delegated roles help text and link in modal tests
* Address PR feedback: gate delegated roles by Enterprise license (exclude Entry), hide for system admins, add Personal Access Tokens section heading
* Add e2e tests for delegated admin roles in Manage Roles modal
* Drop unused radio locator from ManageRolesModal page object
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* [MM-70283] Fix mixed custom-section fallback hiding valid fallback settings
When a disabled plugin defined multiple custom settings sections and only
some specified fallback: true, the settings page collapsed every section
into a single "enable the plugin" warning, hiding the fallback-enabled
sections that should remain configurable.
The collapse now only happens when no custom section allows a fallback.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70283] Add test for mixed custom-section fallback
Covers a disabled plugin with mixed fallback sections: the fallback-enabled
section stays configurable, the non-fallback section shows a per-section
warning, and the single collapse warning is not rendered.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70283] Improve mixed custom-section fallback tests
Rename the visibility test to match its assertions, add an
order-independent case, and assert non-fallback section settings are
suppressed.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* [MM-70283] Tighten fallback comment wording
Co-authored-by: mattermost-code <matty-code@mattermost.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Remove the deprecated backward-compatibility aliases introduced in #35445:
SlackAttachment, SlackAttachmentField, ParseSlackAttachment, and
StringifySlackFieldValue. Plugins should now use the MessageAttachment
equivalents directly.
SlackCompatibleBool is retained as it is still actively used.
Claude-Session: https://claude.ai/code/session_01KnMUsaSbm4HQsNEEtH5zp8
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-67157: Remove unused format flag from /license/client endpoint
The `format` query parameter on the `/license/client` (and local
variant) endpoint was effectively dead: it was required but only ever
accepted the single value `old`, returning an error otherwise. This
mirrors the earlier removal of the same flag from `/config/client`,
where the server now ignores the parameter while clients continue to
send `format=old` for compatibility with pre-v11 servers.
The server no longer inspects the `format` parameter, so requests with
no format, `format=old`, or any other value all succeed. The unused
i18n string and the parameter/response documentation in the OpenAPI
spec are removed accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd
* MM-67157: document format=old retention in webapp client
Mirror the getClientConfig comment so the format=old query param on
getClientLicenseOld is not mistakenly removed; clients keep sending it
for compatibility with pre-v11 servers even though current servers now
ignore it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd
* MM-67157: stop sending format=old from webapp client
Now that the server ignores the format parameter on /license/client,
drop format=old from the @mattermost/client getClientLicenseOld call and
update the e2e intercepts/helpers that matched the old query string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Audz4JLNQN5SJxcPwyQBd
* Drop unneeded wildcard from license/client cy.intercept path
The format query param is gone from GET /license/client requests, so
the trailing * used to match it is no longer needed.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix squished author avatar in content flagging RHS post preview card
The author avatar in the post message preview collapsed into a vertical
sliver when the preview was rendered inside a constrained flex container
(the Data Spillage / Content Flagging RHS report card). The avatar column
shares the shrinkable .col__name flex rule (flex: 0 auto; min-width: 0),
so it collapsed and dragged the min-width:0 avatar image down with it.
Pin the avatar column (the .col__name containing .post__img) to its
natural size so the round avatar keeps its correct dimensions.
Co-authored-by: mattermost-code <matty-code@mattermost.com>
* Pin content flagging RHS preview avatar size to prevent squish
The shared avatar rules (img.Avatar { min-width: 0 }) let the profile
image shrink inside constrained flex layouts, so the round avatar in the
content flagging RHS post preview card rendered as a squished sliver.
Pin the preview avatar image to its intended 24px size (min-width +
flex-shrink: 0) and keep its column from shrinking so it always stays
round regardless of available width.
* Remove redundant author-column flex rule in post header
The preview-specific fix (min-width/flex-shrink on the avatar image plus
flex: 0 0 auto on .post__img) already keeps the avatar round in constrained
flex layouts, so the broader .col__name:has(.post__img) rule is no longer
needed. Removing it also keeps the author text column shrinkable.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>