23773 Commits
Author SHA1 Message Date
cbdf4c779b Migrate FeatureDiscovery to Compass FeatureDiscoveryPanel (#39051)
* Migrate FeatureDiscovery to Compass FeatureDiscoveryPanel

Replace legacy SkuTag and layout markup with Compass FeatureDiscoveryPanel
while keeping trial, cloud, and sales logic in the host adapter. Map license
SKUs to panel skuLabel, pass feature SVGs via illustration.children, and use
Compass button actions for contact-sales flows with host-rendered Start trial
CTAs on self-hosted. Preserve existing test ids and trial legal copy keys.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use medium button size in FeatureDiscoveryPanel actions

Post-trial Contact sales / Learn more actions were forcing large
buttons; align them with the panel default and other CTA paths.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Restore FeatureDiscovery SKU tag as opt-in for Compass panel

Pass null when showSkuTag is unset so FeatureDiscoveryPanel does not
fall back to its PROFESSIONAL default. Add unit coverage for both cases.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Simplify AutoTranslation FeatureDiscovery to panel defaults

Drop AdminSection/SectionContent wrappers and CSS that unset
FeatureDiscoveryPanel styles. Keep auto-translation from wrapping
with a non-breaking hyphen in the title and copy.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Revert non-breaking hyphen in AutoTranslation discovery copy

Keep a normal hyphen in the title and body; drop the matching test change.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Bump compass-ui to alpha.15 and use Button asChild for Learn more

Upgrade @mattermost/compass-ui so Feature Discovery Learn more can render
as a styled ExternalLink via asChild instead of nesting an anchor in a
button (fixes hover underline and invalid HTML).

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Reuse SkuTag licenseSkuToLabel for FeatureDiscovery skuLabel

Export the existing SKU→label mapping from SkuTag and delete the
duplicated feature_discovery_sku_label helper.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Drop redundant FeatureDiscovery panel/illustration CSS

Remove width and imageWrapper flex overrides now covered by Compass
FeatureDiscoveryPanel. Keep host-trial overlay and trial extras styles.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Move host Start trial into FeatureDiscoveryPanel actions

Use Compass primary/secondary actions for self-hosted Start trial via
withOpenStartTrialFormModal, and drop the overlay footer CSS that stacked
StartTrialBtn over the panel.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix FeatureDiscovery eslint nested ternary and comment spacing

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Allow data-testid on FeatureDiscovery panel button props

Compass ButtonProps does not declare data-testid; widen the local
primary-action type so Cypress selectors keep working and tsc passes.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Bump compass-ui to 0.1.0-alpha.18.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update snapshots for compass-ui radio class hashes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert the compass-ui alpha.18 bump.

Feature Discovery uses FeatureDiscoveryPanel and Button asChild, which match alpha.15 already on master. Alpha.16 only changed the Radio CSS module hash, and that rewrote unrelated radio snapshots.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Select Feature Discovery titles by test id in Cypress.

The title test id is on a span inside the panel heading, so a div selector misses it.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com>
2026-10-21 09:21:54 -04:00
Matthew BirtchandCursor Agent e3d074ff02 Migrate Toggle to Compass Switch (#39038)
* Migrate Toggle component to Compass Switch adapter

Rewrite components/toggle.tsx to wrap @mattermost/compass-ui Switch with
prop mapping and sibling labels for onText/offText. Update Jest tests and
snapshots for switch/checkbox semantics (role=switch, checked state).

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Place Toggle onText/offText on Compass Switch label

Pass state text through Switch children so Public/Private sits
left of the control and remains clickable for accessibility.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use medium Compass Switch for plugin enable control

PluginEnableButton Toggle size btn-sm → btn-md (Compass Switch medium).

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Stop forwarding legacy btn-toggle classes onto Compass Switch

Toggle is a Compass Switch adapter; call sites still passed
toggleClassName='btn-toggle-primary', which painted the old gray
rectangular track chrome from _toggle.scss behind the Switch.
Remove the prop, drop those call-site classNames, and delete the
unused legacy toggle stylesheet.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix post priority Switch double-toggle in menu items

Clicking the Compass Switch inside ToggleItem fired both Switch onChange
and MenuItem onClick for the same gesture, so the handler ran twice and
the control appeared stuck. Stop click propagation on the switch and add
a unit test covering switch and row activation.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use Compass Switch settings-row for channel settings toggles

Replace custom __text + Toggle header rows with Switch children +
secondaryLabel for configuration, discoverable, and shared-channels
sections. Drop obsolete header SCSS; keep existing testids/behavior.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Migrate Admin Console LineSwitch rows to Compass Switch

Replace Toggle-based LineSwitch with AdminSettingsSwitch (children +
secondaryLabel), covering group mentions, team modes, and channel modes.
Public/Private uses state text as the primary label; domain input stays
as a footer under the switch. Delete unused LineSwitch styles and update
Cypress selectors for role=switch inputs.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Migrate remaining Toggle call sites to Compass Switch

Replace production Toggle adapters with direct @mattermost/compass-ui
Switch usage across new channel modal, post priority, recaps, plugin
enable, user properties, auto-translation, IP filtering, and content
flagging reviewers. Delete the unused Toggle component and its tests.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Document medium Switch size for Admin Console Public/Private row

AdminSettingsSwitch intentionally uses Compass medium (14px) + semiBold
so short state labels like Public/Private match Channel Settings and do
not inherit the old Toggle btn-lg → large (16px) default.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix Team Modes separator when Sync Group Members is on

Omit the Sync row section separator only when no later row is visible.
ABAC still renders disabled while synced, so last must not track isSynced alone.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use static Public/Private Switch title in Channel Management

Stop swapping Switch children between "Public" and "Private". Keep a
static "Public channel or private channel" label with help text as
secondaryLabel; checked remains public and unchecked private. Update
Cypress assertions and channel_modes snapshots accordingly.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Show current Public/Private mode on channel Switch label

Use a dynamic primary label (Public channel / Private channel) so
checked state is scannable without a segmented control.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Update Cypress for dynamic Public/Private Switch labels

Assert Public channel when checked and Private channel when unchecked.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix plugin enable Switch alignment next to actions menu

Zero Bootstrap label margin-bottom and Switch width:100% on the
PluginMetadataPanel enable control so it lines up with the ⋯ button.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use segmented Public/Private control in Channel Management

Replace the ambiguous Switch for channel privacy with a temporary
AdminSettingsSegmentedRow (Public | Private) until Compass ships a
segmented control. Update Cypress selectors accordingly.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix IP Filtering enable Switch not revealing edit config

Use event.checked for the Compass Switch, skip the filterToggle sync
effect while ipFilters is still null so an empty [] does not race the
fetch, and show EditSection whenever filters are loaded and the toggle
is on (do not wait on currentUsersIP).

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Increase Public/Private segmented control border radii

Round the track to 8px and active segments to 4px for a softer
Channel Management Public/Private control.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Increase Public/Private segmented control track padding

Bump &__segments padding from 2px to 4px.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Soften Public/Private segmented control border

Lower track border opacity from 0.16 to 0.08.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Darken Public/Private segmented control track background

Raise &__segments background opacity from 0.04 to 0.08.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Address review comments on Switch a11y and formatting

Align LDAP Cypress private-toggle assertion indentation, add
aria-labels on IP Filtering and Auto-translation switches, and fix
AdminSettingsSwitch import order for eslint import/order.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix eslint failures blocking Web App CI check-lint

Add blank line before comment in channel_modes and reorder imports
in post_priority_picker_item.test to satisfy import/order.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Align en catalogue isPublicDescr with segmented control copy

Update admin.channel_settings.channel_details.isPublicDescr to say
"Use this control" instead of "Use this switch", matching channel_modes.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix Switch migration CI failures in unit and Playwright tests

Update recap configuration queries to role=switch, refresh
channel_details snapshots for the Public/Private segmented control,
and assert/set Compass Switch via aria-checked check/uncheck in
Playwright helpers and related specs.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Stabilize Switch snapshots and fix Playwright check interception

Mock Compass Switch CSS-module styles in Jest so snapshots no longer
embed install-specific content hashes, and use force:true on Switch
check/uncheck so the wrapping label does not intercept pointer events.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Update Channel Management Public/Private docs for segmented control

Replace toggle wording with select Public or Private, and refresh the
System Console screenshot to match AdminSettingsSegmentedRow.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Revert Public/Private docs wording; keep screenshot refresh only

The step text can stay as toggle between Public or Private; only the
stale screenshot needed updating for the segmented control.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Refresh Sync Group Members channel docs screenshot for Compass Switch

Replace the stale Channel Management flip-toggle image with an updated
System Console capture showing Sync Group Members enabled.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Refresh Anyone can join this team docs screenshot for Compass Switch

Replace the stale Team Management flip-toggle image with an updated
System Console capture showing Anyone can join enabled.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Refresh specific email domains team docs screenshot for Compass Switch

Replace the stale Team Management flip-toggle image with an updated
System Console capture showing email-domain restriction enabled.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Refresh Public/Private channel docs screenshot for segmented control

Replace the Channel Management Public/Private docs image with an updated
System Console capture showing the Public|Private segmented row.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Refresh Sync Group Members team docs screenshot for Compass Switch

Replace the stale Team Management flip-toggle image with an updated
System Console capture showing Sync Group Members enabled.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix Playwright Switch assertions for join/leave and scheduled recaps

Assert join/leave defaults with toBeChecked instead of the legacy active
CSS class, and pause/resume scheduled recaps via click plus polled
expect helpers so async-controlled Compass Switches can settle.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Bump @mattermost/compass-ui to 0.1.0-alpha.15

Upgrade the Compass UI dependency used by Switch and related admin
console controls.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Restore currentUsersIP gate for IP Filtering EditSection

Keep the Toggle→Switch migration scoped to the adapter swap. Showing
EditSection still requires the user's IP to be loaded, matching prior
behavior.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Fix plugin install Cypress asserts for Compass Switch aria-checked

Plugin enable controls use Switch (aria-checked), not segmented buttons
(aria-pressed). Update MM-T5714/MM-T5715 assertions accordingly.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Revert IP Filtering filterToggle sync to master behavior

The ipFilters === null early return and consolidated map were outside
the Toggle→Switch swap scope and mildly changed load-time behavior.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-21 08:18:05 -04:00
Ben SchumacherandClaude Opus 5.5 d87f9beedb [MM-71163] Enable the metrics plugin by default when Performance Monitoring is enabled (#39142)
* MM-71163: Enable the metrics plugin by default when performance monitoring is enabled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* MM-71163: Leave metrics plugin state unset while performance monitoring is off

Writing an explicit false on first load meant existing installs never got the
plugin enabled when performance monitoring was turned on later. Also apply
LogSettings defaults before PluginSettings, which reads them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:37:09 +02:00
cursor[bot] 0cfb066fe4 drip: AppError -> error: password migration (#39312) 2026-10-21 06:31:48 -03:00
82b042b9b2 Redesign and refactor system console logs view (#35569)
* Overhaul Server Logs admin view for better sysadmin experience

Rewrites the Server Logs view from the ground up to address major
usability issues: truncated messages, wasted screen space, no visual
hierarchy, no live updates, and painful navigation on busy servers.

Key changes:
- Rewrite all components as functional React (hooks) replacing class components
- Replace rigid DataGrid table with purpose-built log viewer layout
- Color-coded log level badges (error=red, warn=amber, info=blue, debug=gray)
- Row background tinting for error/warn entries
- Inline row expansion replacing modal-only "Full Log event" workflow
- Compact timestamps (HH:MM:SS.mmm) with full timestamp on hover
- Log level quick-filter toggle buttons with counts (like browser DevTools)
- Configurable page size (50/100/200/500, default 200)
- Polling-based live tail with configurable interval (2s/5s/10s/30s)
- Time range presets (5m/15m/1h/24h) using existing API date params
- Search highlight with match count display
- Keyboard navigation (j/k, Enter expand, e jump to error, / search)
- Text wrapping and compact/comfortable density toggles
- Preferences persisted to localStorage
- Empty state with clear-filters action
- Warn-level color coding added to plain text view
- Updated tests for new component structure

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Update package-lock.json after dependency installation

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix four UX issues in server logs view

1. Pagination buttons no longer shift - page indicator has fixed min-width
2. Keyboard shortcuts shown via hover tooltip on ? button instead of
   raw unicode in footer
3. Empty toolbar buttons replaced with text labels (Compact/Comfortable,
   Wrap/No wrap) since compass-icons lacks line-spacing/wrap icons
4. Top banner buttons properly spaced with vertical separators between
   format toggle, time presets, live tail, and action buttons

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix remaining UX issues in server logs view

1. Remove Compact/Comfortable density toggle - always use compact mode
2. Fix pagination button shifting - footer-info and page-indicator have
   fixed min-widths, left group keeps all pagination controls together
3. Replace invisible ? tooltip with proper keyboard shortcut hints
   rendered as styled kbd elements in the footer
4. Restyle Reload/Download buttons as small tertiary buttons with icons
   (icon-refresh, icon-download-outline) to match the rest of the toolbar

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Redesign server logs toolbar for a cleaner, more professional layout

Move all toolbar controls into LogViewer component with two-row layout:
actions row (time presets, live tail, reload, download) and filter row
(search, level pills, wrap toggle). Rewrite SCSS to match new structure
with unified action-btn style, proper toolbar grouping, and per-level
color-coded pill states.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix time filter using local time instead of UTC

The formatDate helper used local time methods (getHours, etc.) but
appended a +00:00 UTC offset, causing the date range to be shifted
by the user's timezone offset. Use UTC methods to match the offset.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Enhance log detail view with copy buttons and admin deep links

- Add inline copy-to-clipboard buttons on ID fields, caller, URL, and
  other copyable values
- Deep link user_id, team_id, and channel_id to their admin console
  pages (only when value matches Mattermost 26-char ID pattern)
- Split detail view into core fields section and extra fields section
  with a visual separator for better hierarchy
- Add "Copy log line" action alongside existing "Copy JSON"
- Style copy buttons as subtle icons that appear inline with values

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix plain text toggle not fetching logs on format switch

onLogFormatToggle only set the isPlainLogs state but never triggered
a data fetch, so switching to plain text showed an empty view and
switching back to JSON showed stale data. Now explicitly fetch the
appropriate log format on toggle.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Rewrite plain text log view with toolbar and better UX

Rewrite PlainLogList as a functional component with:
- Toolbar with Reload, Download, Wrap toggle, and Copy All buttons
  matching the JSON viewer's design language
- Proper footer with page info, chevron pagination buttons, and a
  "Scroll to bottom" button that appears when you scroll up
- Smart follow-tail: auto-scrolls on new data, disengages when you
  scroll up manually, re-engages when you scroll back to bottom
- Color-coded error/warn lines with hover highlight
- Theme-aware styling (no more hardcoded white background)
- Dedicated SCSS file instead of relying on legacy admin-console styles

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix null crash in PlainLogList when logs prop is null

Redux state can be null before the plain logs fetch resolves, causing
a TypeError on .length access. Default to empty array.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Add line numbers, syntax highlighting, and fix wrap in plain text view

- Fix wrap adding newlines: changed from white-space pre-wrap on the
  line div to wrapping only the inner text span, and removed padding
  that caused visual offset
- Add non-selectable line numbers via CSS ::before pseudo-element with
  user-select: none, so Ctrl+A copies only log text
- Add line number toggle button in toolbar
- Add JSON/JSONL syntax highlighting: keys in blue, strings in green,
  numbers/bools in amber, brackets dimmed
- Add plain log format highlighting: timestamp dimmed, level tags
  color-coded (error=red, warn=amber, info=blue, debug=gray),
  key=value pairs with colored keys
- Memoize highlighted lines to avoid re-rendering on scroll/toggles

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix syntax highlighting contrast and page info unicode escape

- Replace CSS variable colors with hardcoded high-contrast values for
  syntax highlighting (GitHub-style palette): keys #0550ae, strings
  #0a3069, numbers #953800, error #cf222e, warn #9a6700
- Fix \u00B7 rendering literally in page footer by using the actual ·
  character in the defaultMessage string

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Add sort order toggle and improved pagination to plain text view

- Fix line number button being shorter than siblings by adding a
  text label ("Lines")
- Add newest-first/oldest-first sort toggle (client-side reverse)
  with sort-ascending/sort-descending icons
- Replace simple prev/next with full pagination: first page, prev,
  clickable page number, next buttons
- Click the page number to open a "Go to page" input with Enter to
  navigate and Escape to dismiss
- Line numbers stay correct when reversed (count down from total)
- Pass goToPage callback from parent logs.tsx

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix missing icons for sort toggle and first-page button

Replace non-existent compass icons with available alternatives:
- sort-ascending/sort-descending -> arrow-up/arrow-down
- page-first -> « (double chevron character) with styled span
These icons were not in the @mattermost/compass-icons set.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix all lint errors and add PlainLogList tests

- Fix 29 ESLint errors: unused imports, prop placement, regex wrapping,
  mixed operators, ExternalLink usage, nested ternaries, consistent-return
- Fix 47 Stylelint property order errors in both SCSS files
- Add 17 tests for PlainLogList component covering rendering, pagination,
  toolbar toggles, go-to-page, and empty/loading states

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Address code review findings

- Extract shared LogObjectWithAdditionalInfo type into types.ts
- Remove unused onFiltersChange prop from LogList and dead code in logs.tsx
- Fix potential interval stacking in useLogPolling visibility handler
- Add error handling for navigator.clipboard calls (non-HTTPS fallback)
- Fix highlightSearchTerm to highlight all occurrences, not just first
- Remove unused compact prop from LogRow (was always hardcoded true)
- Translate hardcoded strings in CopyButton and DetailValue with useIntl
- Fix time preset buttons to use intl.formatMessage for i18n

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix recursive clipboard call, LogObject reference, and stylelint errors

- Fix copyToClipboard calling itself instead of navigator.clipboard
- Move import to before function declaration
- Replace unimported LogObject with LogObjectWithAdditionalInfo
- Fix CSS property order in _admin-console.scss

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix duplicate i18n message IDs with different defaultMessages

Rename admin.logs.loading and admin.logs.noLogs in plain log view
to admin.logs.plain.loading and admin.logs.plain.noLogs to avoid
conflicts with the JSON log view which uses different default text.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Persist log format preference and rename JSON to Structured

- Remember the user's Structured/Plain text choice in localStorage
  so it persists across page navigations and sessions
- Rename "JSON" label to "Structured" since the view now offers
  filtering, live tail, keyboard nav, and expandable detail rows
- Only applies when the server supports both formats (toggle visible)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix review findings: accessibility, type safety, polling, and theme support

- Add aria-labels to icon-only pagination and clear-preset buttons
- Reset page/expandedIndex/focusedIndex when logs dataset changes
- Fix extraFields filter to use != null instead of truthiness check
- Guard handleKeyDown from bubbling up from interactive descendants
- Refactor copyToClipboard to return Promise, set success only on resolve
- Fix live-tail with time presets by recomputing date range dynamically
- Fix duplicate reload on mount with hasMountedRef guard
- Fix LogObjectWithAdditionalInfo index signature to use unknown
- Add immediate fetch when polling enables
- Guard against duplicate intervals in visibility change handler
- Use ICU number formatting in i18n strings
- Replace hardcoded colors with theme-aware CSS variables
- Extract isPlainChar helper for readability
- Show copy failure feedback in plain log view

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Replace custom JSON highlighter with highlight.js

Use the project's existing highlight.js dependency and SyntaxHighlighting
utility for JSON syntax highlighting in the plain log viewer. This:

- Removes ~120 lines of custom JSON tokenizer (highlightJson, findStringEnd,
  isPlainChar) and 5 custom CSS classes
- Uses async highlight.js with the user's selected code theme (github,
  monokai, solarized-dark, solarized-light) for consistent styling
- Renders JSON lines with dangerouslySetInnerHTML inside .hljs container
- Shows sanitized HTML as initial fallback while highlight.js loads the
  JSON grammar asynchronously
- Keeps custom highlightPlainLog for Mattermost's [LEVEL] key=value format

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix hljs background override in plain log viewer

The highlight.js theme CSS sets background on .hljs elements (typically
white), causing JSON log lines to have a white background instead of
inheriting from the log viewer. Override to transparent.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Update webapp/channels/src/i18n/en.json

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add visible focus indicator to search input in log viewer

The search input had outline: none with no replacement focus style,
making it invisible to keyboard users. Add :focus-within on the
wrapper to show a border + box-shadow ring matching the theme's
button-bg color when the input is focused.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Use React Router Link for admin deep links in log row

Replace plain <a href> with React Router <Link to> for admin console
deep links (user_id, team_id, channel_id). This ensures links work
correctly on subpath deployments where the app is served from e.g.
/mattermost, since React Router automatically prepends the basename.
Also gives client-side navigation instead of full page reloads.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix follow-tail to honor newestFirst sort order in plain log viewer

When newestFirst is true, the tail (newest entries) is at the top of
the scroll container. Updated three locations:
- Auto-scroll useEffect: scrolls to top when newestFirst, bottom otherwise
- handleScroll: detects proximity to top as "at tail" when newestFirst
- Scroll-to-tail button: scrolls to top and shows up arrow when newestFirst

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Refactor copyToClipboard to guard navigator.clipboard and propagate errors

- Check for navigator.clipboard availability before calling writeText,
  rejecting the promise if unavailable instead of silently swallowing
- CopyButton now awaits the promise before showing success feedback
- All copy handlers (handleCopyJson, handleCopyLine, handleKeyDown 'c')
  now have .catch() handlers so unhandled rejections don't occur

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Clamp page index and clear stale selection on dataset changes

Previously the reset effect always set page to 0 when logs changed,
losing the user's position on reloads and live-tail updates. Now:
- Filter/sort changes still reset to page 0
- Dataset changes (reload, live-tail) clamp page to valid range
- handlePageSizeChange also clears focusedIndex (was missing)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Guard navigator.clipboard in handleCopyAll to prevent TypeError

Check navigator.clipboard?.writeText exists before calling it. In
non-secure contexts where the Clipboard API is unavailable, show the
failure state immediately instead of throwing a synchronous TypeError.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Preserve wall-clock time in formatTimestampShort

Extract the time substring directly from the raw timestamp string via
regex before attempting Date parsing. This avoids toISOString() converting
to UTC and losing the original timezone offset from the server logs.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Fix "1 lines" pluralization in plain log viewer page info

Use ICU plural syntax so the message reads "1 line" (singular) and
"N lines" (plural) instead of always showing "lines".

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Align header column widths with row layout in log viewer

Header widths were mismatched with row columns:
- header-level: 40px → 32px (matches LogRow level badge)
- header-timestamp: 100px → 92px (matches LogRow timestamp)
- header-caller: 200px → 216px (192px caller + 8px gap + 16px expand indicator)

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* Prevent overlapping poll fetches in useLogPolling

Added an isPollingRef guard so tick() returns immediately if a previous
fetch is still in-flight. Applied to both the main polling effect and
the visibility-resume branch to prevent concurrent fetches when
fetchLogs takes longer than intervalMs.

https://claude.ai/code/session_012NLTVDoJnzFJDRduBKrxv9

* fix linter errors

* Fix post-merge lint and E2E fallout from master

- useMemo(loadPrefs, []) -> inline arrow, for react-hooks/use-memo which
  landed with the ESLint 9 upgrade (#37039).
- Restore the trailing comma in logs.tsx dropped while resolving the
  conflict, for @stylistic/comma-dangle (#36770).
- Update the MM-T908 Cypress spec, which still asserted on .LogTable and
  the old banner reload button; both were replaced by the LogViewer
  markup on this branch.

* Remove CSS orphaned by the log viewer rewrite

.log__panel, .banner-buttons (incl. the __separator this branch added)
.LogTable and .plain-log-list no longer match anything: the class
components that rendered them were replaced by LogViewer/PlainLogViewer,
which carry their own flex sizing in log_list.scss / plain_log_list.scss.

* Add unit tests for LogRow and useLogPolling

The log viewer rewrite added log_row.tsx and use_log_polling.ts without
tests, while its sibling plain_log_list.tsx got a dedicated suite.

LogRow (51 tests): expand/collapse by click and Enter/Space, the `c` copy
shortcut, the copy-line format, search highlighting, extra field
rendering with admin console deep links, level classes, and the
stopPropagation that keeps the copy controls from toggling the row.

useLogPolling (21 tests): the immediate fetch on enable, interval
restarts, the re-entrancy guard, and the visibilitychange pause/resume.

The advance() helper flushes microtasks before advancing the fake
timers, otherwise the re-entrancy guard swallows the next tick and
several tests pass for the wrong reason. The resume test asserts on
jest.getTimerCount() rather than call counts, because counting fetches
cannot distinguish one interval from two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Harden the server logs live-tail polling

A failed poll rejected out of the interval callback with nothing to
catch it, so any 500 or network blip from /api/v4/logs during live tail
surfaced as an unhandled promise rejection in the admin's browser.
Catch it and let the next tick recover.

Also collapse the two copies of the tick closure into one, so the
visibilitychange resume path cannot drift from the main effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Address review feedback on the server logs viewer

Restore the search debounce (PR review): the rewrite filtered on every
keystroke with the input value owned by the parent, so typing was gated
on a pass over every fetched log entry. /logs/query sends no pagination,
so the server returns up to LogsPerPageDefault (10000) entries per node.
The input value is now local to LogList and the term is debounced
upwards at 200ms, matching the interval the class component used.
Clearing bypasses the debounce so it feels immediate.

Fix the grid semantics (PR review): rows carried role='row' with plain
spans as children, the column header sat outside the role='grid'
container with no columnheader roles, and the empty state was a
non-row child. Drop to role='list'/'listitem', which matches the
row-level arrow key navigation that is actually implemented, and move
the disclosure semantics onto the row summary so aria-expanded sits on
a role that supports it. The keyboard navigation now focuses
.LogRow__main, and the empty state and refreshing indicator moved out
of the list so it only owns list items.

Also stop background polls from touching the loading state, and hoist
the repeated date formatting and filter constants.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Extract the remaining hardcoded strings in the logs viewer

The rewrite left literal strings in the level filter pills, the plain
text viewer controls and several titles. Move them to defineMessages so
they are translatable and extractable, and sync en.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the expanded log row stable across reloads

The expanded row was tracked by its index in the visible page, so a
reload or a live-tail poll either collapsed it or moved the expansion to
whatever log now sat at that index. Track it by content instead, and
give repeated identical lines distinct React keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Remove the stale User/Token ID lookup hint from the logs page

The banner told admins to look up a User ID or Token ID by pasting it
into the User Management search filter. That search is the user report
query, whose searchable columns are Username, FirstName, LastName,
Nickname and Email, so neither kind of ID matches anything.

It worked until the new User Management screen replaced the old page in
03f71c9c84 ("[MM-54296] Admin Reporting for Users"), which dropped the
getUserByTokenOrId lookup behind the search box. The hint has pointed at
missing behaviour ever since.

The user_id, team_id and channel_id fields in an expanded log entry now
link straight to their admin console pages, which covers the useful half
of the advice. Token IDs have no path yet; that needs its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop j/k log navigation and fix caller column clipping

Arrow keys already cover row navigation, so the j/k vim bindings were
redundant; the hint bar now shows the arrow keys instead.

The caller column was rigidly fixed-width, so on narrower viewports it
(and the expand indicator after it) got clipped by the list's
overflow-x: hidden with no visual indication. It now shrinks in favor
of the message column, and truncates from the start so the useful
file:line suffix stays visible; a title attribute exposes the full
path on hover.

* Add regression test for stable expanded row; dedupe plain log sort tooltip

Covers the content-keyed expanded-row behavior with a test that
rerenders with an equal-but-new logs array, matching what a reload or
live-tail poll hands down. Also lifts the plain log view's sort
tooltip strings into defineMessages so they aren't redefined on every
render.

* Fix review findings on the logs view

- Advance the live tail "last updated" label with its own clock, so the
  elapsed seconds actually move between polls instead of being pinned to
  "just now"
- Give the plain viewer's go-to-page input an accessible name
- Disable the log spinner, live-tail pulse and row expand animations under
  prefers-reduced-motion
- Dedupe the time preset range calculation into a helper
- Assert the log list owns only list items by comparing against its children,
  which a semantic <li> would have slipped past
- Repoint the Cypress spec at the log format toggle: it still asserted on the
  banner this branch removed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add Playwright coverage for the server logs page

Covers what the RTL suite cannot: that entries written to the real server log
reach the page, that live tail polls them in without a manual reload, that the
log format choice survives a reload, and that time presets go out in the layout
the server parses.

The date format assertion is deliberately strict. isLogFilteredByDate silently
falls back to a zero time on an unparseable date_from rather than rejecting it,
so a format change would return 200 with a wider result set and no test would
notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fix plain log line numbering and expose the Lines toggle state

The plain logs API pages backwards from the newest line, so numbering by
page offset made older pages count higher than newer ones: the gutter
ascended within a page but descended across them. A single page cannot know
its position in the file, so number lines relative to the page instead.

Also add aria-pressed to the Lines button, whose label does not change with
its state. The sort and wrap buttons already carry their state in their
label, so the attribute would contradict them there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Expand only the clicked log row

The row identity used for expansion was timestamp|caller|msg, which is not
unique: concurrent HTTP requests log the same message from the same caller
inside the same millisecond, so clicking one row expanded every entry that
shared those three fields. Key on the whole entry, and fall back to the
row's disambiguated key so byte-identical entries stay independent too.

While here, read the visible rows through a ref in the row callbacks. They
depended on an array rebuilt every render, so their identity changed every
time and memo(LogRow) never held - every row re-rendered on each parent
render, including the once-a-second live tail clock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Assert which row expanded in the identical entries test

Counting the expanded rows passed even when the wrong one opened, which is
the failure the test is named for. The entries carry no distinguishing
content, so check the details by row position instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep the log selection on the same entry across reloads

The focused row was tracked by its position in the visible page, so any
poll or reload that shifted the dataset left the highlight pinned to a
screen position while the entry it belonged to moved underneath it. Track
it by content key instead, the way the expanded row already is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Cover newest-first polling and DOM focus in the selection tests

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Offer 5s, 10s, 30s and 60s as live-tail intervals

Drop the 2s option, which polls the logs API harder than the page needs,
and add 60s for a slow tail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Test the log list filters, sorting, pagination and preferences

Covers the logic that only lived in LogList: the level pills including the
solo and last-level-on rules, the sort toggle, paging and page size with the
clamp that runs when a poll shrinks the dataset, the stored preferences
including malformed values, and the e and / shortcuts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Order the server logs spec imports the way the linter expects

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Default server logs to newest-first and hide decorative icons from a11y tree

Oldest-first with page 0 shown by default meant a newly written entry
could land past the visible page once the log file had more than 200
lines, which is why live tail and the e2e assertions on a freshly
written marker were failing. Default to newest-first instead. Also add
aria-hidden to decorative icons; one of them (the poll-interval
chevron) was leaking into its button's accessible name and breaking an
anchored-regex Playwright locator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Align server logs page with Mattermost admin console design

Rebuild the log viewer chrome on the shared admin console primitives instead
of a bespoke design system:

- Render the viewer inside .admin-console__container so it reads as the same
  white card the other admin console list pages use.
- Replace the custom .LogViewer__action-btn family with btn btn-tertiary /
  btn-quaternary / btn-primary / btn-icon.
- Replace the search field with the shared Input widget, and the level pills,
  time presets, poll interval and log format radios with Menu.Container
  selectors matching the System Users filter row.
- Match the column header and footer metrics of .adminConsoleListTable, and
  move keyboard shortcut hints from a kbd strip into a tooltip.
- Drop the -apple-system font override so the chrome inherits Open Sans, and
  keep monospace for log content only.
- Use the design tokens for radii, elevation and level colors.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Fold live tail into one selector and update tests for the new markup

The live tail on/off toggle and its poll interval were two link-styled buttons
sitting loose in the filter row; they are now a single Menu selector matching
Levels, Duration and Log format, where picking an interval is what starts
polling. Also gives the core detail fields their own column widths so the
level chip does not leave a gap.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Lead the server logs filter row with the live tail selector

Live tail decides whether the list is a snapshot or a stream, so it now comes
first, ahead of the controls that narrow what the stream contains. Its elapsed
status moves up with it and reserves its width, so the ticking seconds cannot
nudge the filters beside it.

Co-authored-by: ben.schumacher <ben.schumacher@mattermost.com>

* Reorder server logs filter row: search, levels, live tail, duration, log format

Moves the live tail selector after the levels menu so the filter row reads
search -> levels -> live tail -> duration -> log format, and drops the
"Updated Xs ago" live tail indicator along with its now-dead polling clock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix unused intl lint error in logs.tsx

useIntl's only remaining use was formatting the removed live-tail
"last updated" text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Count live intervals directly in the useLogPolling resume test

React 19's async act() leaves a pending timer behind, so jest.getTimerCount()
no longer equals the number of polling intervals.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Match the log detail copy buttons and monospace the detail values

Both copy actions use btn-quaternary with the copy icon; icon-code-json
does not exist in compass-icons. Timestamp and message in the expanded
details now use the monospace font like caller.

* Keep same-millisecond log entries in order when sorting

Ties now fall back to file order, so newest-first reverses entries that
share a timestamp instead of leaving them oldest-first. Timestamps are
rewritten to ISO 8601 before parsing since Safari rejects the server
format.

* Show server logs shortcuts as a key list and drop the next-error shortcut

The shortcuts tooltip was one long sentence; it now lists each shortcut
next to its key caps, and documents Esc. E / Shift+E are removed since
few would discover them and the Levels filter covers finding errors.

* Wrap the expanded log row instead of repeating its message

The expanded details repeated the row's message and level chip. The row
now wraps its message while expanded, and the details keep only the full
timestamp, caller and extra fields. Finishing a text selection inside the
row no longer toggles it.

* Reuse ShortcutSequence for the server logs keyboard hints

* Drop the 5 second live tail interval and default to 30 seconds

Every poll is a full logs query that scans the log file on every cluster
node, so a 5 second interval puts sustained load on the server.

* Derive server log fetches from state and run them in order

Every fetch now goes through one reload that reads the current format,
page, time preset and levels, triggered by an effect on those values:

- the enabled levels are sent with the query again, so filtering by level
  isn't limited to the newest 10000 lines
- switching back to structured logs recomputes the time preset's range
- fetches are queued, and a queued fetch replaced by a newer one is
  skipped, so a slow response can't overwrite a newer one
- the search filter moved into LogList so the footer total counts every
  loaded entry

* Format nested log fields, end presets at server time, trim comments

- nested objects in extra fields render, search and key as JSON instead
  of [object Object]
- time presets no longer send an end date, so the server's clock ends
  the range and browser clock skew can't drop the newest entries
- row keys are memoized and unchanged entries keep their object, so
  memo(LogRow) skips them after a poll
- multi-line and narrating comments in server_logs cut to one line

* Show the user behind user ID fields in the server logs view

User ID fields in an expanded log row now show the avatar and @username
next to the ID, and the admin console link moves from the ID to the user.

* Share one clipboard hook in the server logs view and drop dead code

- useClipboardCopy replaces the copy handlers in LogRow and PlainLogList,
  and falls back to the shared copyToClipboard where the Clipboard API is
  missing (plain HTTP), where copying used to fail
- useLogPolling no longer tracks lastUpdated, which nothing read
- full_log_event_modal is no longer imported anywhere

* Fix server logs live tail e2e test after removing last-updated indicator

The "Updated Xs ago" span was dropped from the filter row, so the test
now checks the live tail selector's value instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: describe redesigned server logs filters and live tail

Rename the JSON log format option to Structured, replace the full log
event modal reference with inline row expansion, and document the
duration filter and live tail added by the server logs redesign.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Expect an open-ended date_to in the server logs time range e2e test

Presets now send only date_from so the server ends the range at its own
clock; the test still required a formatted date_to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-21 10:08:38 +02:00
346c94f0d6 Normalize changelog generator output, handle major-version rollover, and keep reviewer edits on reruns (#38478)
* Tighten changelog generator formatting rules and normalize output

Refines the SYSTEM_PROMPT in the changelog generator and adds a
deterministic post-processing pass so generated entries match the
established changelog format without manual cleanup.

SYSTEM_PROMPT changes:
- State the exact bullet indentation convention: one space for
  top-level bullets, three for nested ones. The previous instruction
  asked for two spaces, which did not match the changelog file.
- Give the Database Schema Changes subsection an explicit shape: a
  summary bullet naming the release, with each individual change
  nested beneath it.
- Extend the config.json block with a removed-setting example and
  spell out that removed settings are top-level bullets rather than
  nested under a plan grouping.
- Show the Improvements section verbatim so the blog post line lands
  immediately after the heading with no blank line between them.
- Strengthen the prohibition on horizontal rules, which break the MDX
  document, into an explicit NEVER in both the section-structure and
  markdown-formatting rules.

Add normalize_formatting(), applied to the model output before the
entry is assembled. The prompt specifies all of these, but compliance
is not guaranteed and each mistake produces a visibly wrong diff:
- strips standalone horizontal rules
- reduces even bullet indents by one (2 -> 1, 4 -> 3)
- closes a blank line between ### Improvements and its blog post line
- collapses the blank-line runs left behind by rule removal

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Escape MDX-unsafe characters in generated changelog prose

The changelog is an .mdx file, so an unescaped `{` opens a JSX
expression and `<` followed by a letter, `/` or `!` opens a JSX
element. Release-note prose containing text such as `<plugin ID>` or
`{"status": "ok"}` therefore breaks the docs build. The SYSTEM_PROMPT
already instructs the model to wrap such text in backticks, but
compliance is not guaranteed and the failure mode is a broken build,
so escape defensively.

Add escape_mdx_unsafe(), applied to the model output after
normalize_formatting(). It backslash-escapes bare `{` and bare `<`
when followed by a letter, `/` or `!`, while copying protected regions
verbatim: inline code spans (double- and single-backtick) and Markdown
link destinations. A negative lookbehind avoids double-escaping text
that is already escaped. Closing `}` is deliberately left alone -
escaping the opening brace is sufficient to prevent a JSX expression,
and this keeps the generated heading anchor intact.

Escaping runs before BLOG_POST_LINK substitution and Go Version
injection, so the Markdown link and the backticked Go version are
inserted after the escape pass and are never touched by it.

Also harden normalize_formatting(): its horizontal-rule removal would
strip the `---` delimiters of a YAML frontmatter block, which MDX
requires at the very start of a file. A leading frontmatter block is
now split off and restored verbatim, so the function is safe if it is
ever applied to whole file contents rather than just the generated
fragment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Update generate_changelog.py

* Update generate_changelog.py

* Update generate_changelog.py

* Update generate_changelog.py

* Update generate-changelog.yml

* Update generate_changelog.py

* Derive the changelog path in the script and guard version/milestone

Two gaps remained in the major-rollover handling:

The workflow resolves the changelog file from the version, but the script
still fell back to a fixed "CHANGELOG.md" when CHANGELOG_PATH was unset, so
a direct invocation would append a v12 entry to the repository-root
changelog with nothing to warn about it. changelog_path_for_version()
derives the default from VERSION instead, and major_version() now raises on
a version it cannot parse rather than returning an empty string. The script
prints the file it targets so a wrong path is visible in the run log.

Nothing validated the two dispatch inputs against each other. The milestone
selects which PRs are collected and the version selects both the entry
heading and its file, so a mismatch files one release's notes under
another's. The workflow now compares their major versions in its first step
and fails before anything is generated. It compares majors only, so a
deliberate v11.10.0/v11.9.0 pairing still runs.

insert_changelog_entry() also creates the parent directory when writing a
file for a new major that does not exist yet.

Verified: the derived path for v11.9.0/v12.0.0/12.0.0 and the raise for
malformed versions; bootstrapping mattermost-v12-changelog.mdx into a
missing directory writes the frontmatter header before the entry; a second
insert lands after that header and above the first entry; the workflow
parses and the guard is the job's first step; the guard's shell logic
accepts matching majors and rejects mismatched ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gJFVcJtw4rdpjWrmpP9T7

* Rank bullet indents per list block in normalize_formatting

Rule 2 took a single minimum indent across the whole fragment and
treated anything deeper as nested. That had two failure modes:

- Sections are generated independently and can disagree on base indent.
  A fragment with one section at indent 0 and another at indent 1 gave
  base 0, so the depth-1 bullets — already correct top-level — were
  pushed to three spaces and rendered as nested.
- A three-level list collapsed to two, because every indent deeper than
  the minimum mapped to the same three spaces.

Indents are now ranked within each list block, mapping level n to
1 + 2n spaces. Blocks are delimited by lines that are neither bullets
nor bullet continuations, so each section resolves its own levels, and
ranking keeps a third level distinct instead of flattening it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DR6hsSCuRfXqAnyHNY7sKo

* Add a major release type and guard against truncated changelog output

Three fixes on top of this branch, all of which v12.0 exercised in
practice.

release_type had no "major" option. The dispatch input offered only
"feature" and "esr", so generating v12.0 emitted "Feature Release" and
the anchor #release-v12-0-feature-release. Both had to be corrected by
hand, and four docs pages deep-link to #release-v12-0-major-release.
"major" is now a third choice, and the anchor slug is derived from the
heading label instead of being written out separately, so the two cannot
drift apart. A vX.0 release generated under any other release type now
prints a warning naming the heading and anchor it is about to produce.

Truncation. polish_with_ai capped output at 4096 tokens and never looked
at stop_reason, so a response that overran the cap was silently cut
mid-word — the v12.0 entry is ~32k characters, roughly twice the cap.
The cap is raised to 16384 and a max_tokens stop reason is now a hard
error. As a backstop for truncation arriving any other way,
check_for_truncation rejects an entry containing a bullet that ends
without terminal punctuation, before anything is written. It allows the
endings that legitimately occur — a bare inline-code span, a fully bold
label, a Markdown link or parenthetical, a bare URL, and a bullet
continued on the next line — and flags nothing across the 2,702 bullets
in the existing v10, v11 and v12 changelogs.

Identifier fidelity. The same run shortened ``/ephemeral_mode/cleanup``
to ``/cleanup``. A prompt rule now requires endpoint paths, setting
names, commands, table and column names, and feature flags to be
reproduced in full.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DR6hsSCuRfXqAnyHNY7sKo

* Run the truncation check only on AI-polished prose

Applying it to the no-API-key fallback would block legitimate runs: that
path emits PR authors' raw release notes verbatim, and those routinely
end without a full stop. Across the 71 release notes on the v12.0.0
milestone, 4 do — "Fixed deeplink routing" among them — none of which is
truncated.

polish_with_ai now reports whether it actually polished, and the check
runs only when it did. The AI output itself is reliably punctuated: the
188 bullets of the generated v12.0 entry all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DR6hsSCuRfXqAnyHNY7sKo

* Keep reviewer edits on changelog reruns and record each bullet's source PRs

A rerun rebuilt the changelog branch from master and force-pushed it,
discarding every edit made in the PR, and then failed at gh pr create
because the PR already existed. That rules out running the generator
repeatedly while a release is in review.

- Add a mode input. auto (default) updates an open changelog PR in
  place: only notes from PRs its body does not list yet are generated,
  and they are inserted into the matching sections of the existing
  entry as new commits, never a force-push. With no open PR it creates
  one. regenerate keeps the old rebuild-from-scratch behaviour on
  request. dry_run generates only, with nothing pushed.
- Open the PR as a draft.
- Tag each note with an ID and have the model mark every bullet with
  the IDs it used. Markers are stripped before writing, and the
  attribution goes to sections.json (bullets per section with their
  source PRs) and to a changelog-sources comment in the PR body, which
  is how the next run tells new PRs from ones already included.
- Upload entry.mdx, sections.json and pr_body.md as an artifact and
  show the generated text in the run summary.
- Run the generator from the workflow's own checkout, since the PR
  branch may carry an older copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DR6hsSCuRfXqAnyHNY7sKo

* Keep changelog reruns inside the list and refuse unattributed notes

Update mode appended new config bullets after the upgrade admonition and
did not recognize two-space plan labels, so a rerun could split a section
or repeat a plan heading. New bullets now stop at an MDX admonition, and
top-level is the shallowest indent already in that section.

A note with no source marker is no longer recorded as included, because
the next run would skip it. Multi-line raw notes keep their marker, and
star or plus bullets are rewritten to dashes so update mode does not drop them.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Eva Sarafianou <eva.sarafianou@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 08:24:35 +03:00
Combs7th 578fbba586 Docs: Remove obsolete v12 config settings (#39276) 2026-10-20 21:05:57 -07:00
Combs7thandBen Schumacher 459a3af42b Docs: Clarify validated TLS and S3 configuration values (#39284)
* Docs: Clarify validated TLS and S3 configuration values

* Docs: Document config validation constraints added in MM-69349

Reflect the bounds, accepted values, and formats enforced by server
config validation, and fix unclosed paragraph tags in the TLS and S3
rows.

* Docs: Use uncompressed as the WebserverMode value

* Docs: Fix wording of export S3 upload part size

* Docs: Note empty GroupFilter and FeedbackEmail skip validation

* Docs: Fix connected workspace sync defaults and MaxPostsPerSync key

---------

Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
2026-10-20 20:43:07 -07:00
Combs7th d0d6f36df1 Docs: Increase interactive dialog title max length to 80 (#39278) 2026-10-20 20:41:11 -07:00
4d716b67f9 fix(e2e): repair master flakes from the channel intro user limit and tour-tip pulse rings (#38986)
* fix(e2e): repair file_permissions_edge_cases.spec.ts on master

MM-T5828 built its permalink from testConfig.internalBaseURL and assumed the
server's SiteURL still matched. pw.ensureSiteUrl() restarts the Mattermost
container with MM_SERVICESETTINGS_SITEURL pointing at the host-reachable
baseURL, and because that is an env override, initSetup()'s patchConfig cannot
put it back. On a reused server, every spec that runs after an auth/claim or
oidc spec therefore sees a SiteURL the permalink no longer matches, the server
emits no permalink embed, and .post-preview never renders.

Read SiteURL off the running server instead of assuming it.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): share permalinkUrl helper across SiteURL-sensitive specs

Build /pl/ permalinks from the SiteURL the running server reports, via
one helper used by MM-T5828 and the post_list specs that had the same
internalBaseURL assumption.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* fix(e2e): reject empty SiteURL in permalinkUrl helper

Fail fast instead of building a relative /pl/ path when the server
reports no SiteURL.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* fix(e2e): converge SiteURL for demo_dialog interactive dialogs

demo_dialog.spec.ts's two bare-/dialog tests fail on any CI worker that
earlier ran a spec calling pw.ensureSiteUrl(). The demo plugin registers
its dialog callback as <SiteURL>/plugins/<id>/dialog/1, and
App.DoActionRequest only dispatches in-process for relative URLs, so an
absolute one is a real outbound POST. Once SiteURL points at the
host-mapped baseURL the server cannot reach that address from inside its
own container: the submit returns 400, leaving the dialog open, and the
cancel posts no notification.

Add pw.ensureInternalSiteUrl(), which converges SiteURL back to
internalBaseURL and is a no-op on a stack booted with the default, and
call it from the two tests whose callback URL is absolute.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): repair channel_headers.spec.ts MM-T880 on master

A channel intro replaces its action buttons -- favorite, "Set header" and
notification preferences -- with just the invite button while the server has
fewer users than the webapp's usersLimit of 10. On a reused Testcontainers
server that count depends on how many specs the worker ran before, so
MM-T880's click on the intro's "Set header" button only found it by luck.

Add ensureChannelIntroActions(), which tops the server up to that many users,
and call it from the test.

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(webapp): stop tour-tip pulse rings from swallowing clicks

PulsatingDot's ::before/::after rings animate to scale(2.5), 30px across a
12px dot, and were hit-testable. The burn-on-read tour tip puts that dot on
top of the burn-on-read button, so for ~77% of every 2s pulse cycle the
element at the button's own centre is the dot, not the button.

A click whose mousedown and mouseup land on different elements is delivered
to their nearest common ancestor, so when a ring crosses the cursor between
the two, `click` fires on .BurnOnReadControl and neither the button's toggle
nor the tip's open handler runs. That is the intermittent
dm_gm_flow.spec.ts "MM-66742_1 BoR toggle is available in DM channel"
failure on master: the click reports success and the BURN ON READ label
never appears.

The rings are decoration; the dot itself stays clickable.

https://github.com/mattermost/mattermost/actions/runs/37978315300

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>

* fix(e2e): drop the permalinkUrl helper now that master resets SiteURL per spec

#39091's ensureDefaultServerEnv() restores the default SiteURL before each
spec file, so the permalink specs pass with master's own copies (12/12 and
210/210 on a server with a leaked SiteURL). Revert them and remove the helper.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
2026-10-21 03:44:05 +05:30
Scott Bishel 926f347bd5 Interactive dialogs: refresh parent on return, submit_buttons, action button descriptions (#39155)
* Interactive dialogs: stacked dialog refresh, submit_buttons, action button layout

* fix from review comment
2026-10-20 16:49:16 -05:00
de9d77e456 [MM-65788] Make PermanentDeleteUser delete all of a user's data (#38773)
* [MM-65788] Make PermanentDeleteUser delete all of a user's data

PermanentDeleteUser left orphaned rows behind and could abort halfway
through, leaving an account in a half-deleted state.

Posts are deleted in batches with a hard fail-safe, so a user with more
posts than the store can walk had their sessions, preferences, channel
memberships and some of their posts destroyed before the delete failed.
The account is now rejected up front, before anything is removed, with an
error telling the admin how many posts need to go first. The fail-safe
also fired one batch early, so the effective ceiling was 9,000 rather
than the advertised 10,000; it now checks the batch count before deleting
rather than after.

Several tables holding per-user data were never cleaned up at all. These
are now removed along with the account:

  Status, ChannelMemberHistory, SidebarCategories, SidebarChannels,
  ProductNoticeViewState, ThreadMemberships, PostAcknowledgements,
  ReadReceipts, PostReminders, UploadSessions, NotifyAdmin,
  UserTermsOfService, CommandWebhooks, DesktopTokens, SharedChannelUsers
  and OAuthAuthData.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Count posts on master before permanently deleting a user

The up-front post-count gate used AnalyticsPostCount, which reads a replica.
Replica lag could let the gate pass and then fail inside PermanentDeleteByUser
after sessions, preferences, and memberships were already removed. Count on
the writer instead so the 10,000-post limit is checked against the same
database the delete will use.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Stop flaking webhook override setup on a no-op Save

MM-T622 failed twice in Cypress because the helper visited the admin
console and waited for Save to enable after checking radios that were
already in the desired state. Set EnablePostUsernameOverride and
EnablePostIconOverride through the config API instead, matching the
other incoming webhook specs.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Retrigger CI after Enterprise docker image setup failed

Enterprise tests and style were already green; ci / Build docker image
exited during cosign install (curl 35). gh run rerun is not available,
so this empty commit starts a new Enterprise image build.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Patch webhook override flags without resetting config

apiUpdateConfig merges e2e defaults, which moved MM-T622 off the
save-button flake and onto a wrong webhook avatar (static file instead
of the image-proxy URL). PUT the current config with only
EnablePostUsernameOverride and EnablePostIconOverride changed.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Skip no-op Save when webhook override radios are already set

Setting EnablePost*Override through the config API left MM-T622 asserting
the default webhook avatar instead of the image-proxy icon URL. Keep the
admin-console path that the rest of the incoming-webhook suite uses, and
skip Save when both radios are already in the requested state so it does
not time out on a disabled button.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Wait for webhook override flags after config PUT

MM-T622 still showed the default webhook avatar after the flags were
written: username override applied but EnablePostIconOverride was stale
when the incoming webhook was posted. PUT only those two flags (no
e2e-default merge, no no-op admin Save) and wait until GET config
reports both before continuing.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Give MM-T622 a hook-level icon after enabling overrides

PUT+waitUntil on EnablePostIconOverride still left the webhook avatar at
DEFAULT_WEBHOOK_LOGO: username override applied but override_icon_url was
missing. MM-T620 passes on the same fleet by setting the hook icon in-app
after the flags are on. Mirror that, and point at the stable fixture used
by the markdown image specs (icon_WS.png on mattermost.com is 404).

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* [MM-65788] Advance the schedule calendar when the target day is next month

On the last days of a month, dayFromToday=2 lands on the 1st of the next
month. The helper still saw day 1 of the current month (visible but
disabled) and clicked it instead of going forward. Always go to next
month when the target month differs, and pick a future day in the demo
plugin date dialog.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 0 answered, 1 resolved, 0 declined

Make PermanentDeleteUser resumable instead of rejecting users up front:
- Drop the up-front post count and PostCountOptions.UserId/UseMaster.
- Delete posts in batches of 1,000 per transaction, replies included,
  with at most 10,000 of the user's posts per call; hitting the limit
  returns an error asking the admin to run the delete again.
- Delete post-keyed data (reactions, read receipts, acknowledgements,
  priority, persistent notifications, reminders) for the deleted posts
  and every reply to them in the same transaction.
- Remove files from storage before deleting their FileInfo rows, listed
  from master including soft-deleted files and files on replies.
- Delete the Users row last, and report any failure after deactivation
  as a partial delete that is safe to re-run.
- PermanentDeleteAllUsers reports the users it could not finish.

* Address PR feedback: 0 answered, 3 resolved, 0 declined

- Split post-keyed deletes in permanentDeleteAssociatedData into separate
  statements for the post ids and their replies, so Postgres can use the
  PostId indexes instead of scanning each table.
- Combine the arms of FileInfo GetForPermanentDeleteByUser with UNION
  instead of OR so the post-based arms use idx_fileinfo_postid_at.
- Permanently deleting a user now also removes their property values
  across all groups, scheduled recaps, recaps with their channels, and
  channel join requests.

* Address PR feedback: 2 answered, 7 resolved, 0 declined

* Remove the profile image directory even when the image is already gone

A removal that deleted profile.png but then failed to remove the
directory it sat in left that directory behind permanently: the retry
checked for profile.png first, found it absent, and returned without
removing anything. Removing the directory is now unconditional, which
every file backend tolerates and which matches the rest of
permanentDeleteUserData being safe to repeat.

The assertion in requireFullyDeleted was also checking only the image
file, so it could not see the leftover directory. It now checks the
directory, which covers both.

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>
2026-10-20 15:53:26 -04:00
46ed11842e Stricter i18n catalog checkers (#39275)
* Fail an empty translation even under the warn-missing flags

CI runs both checkers with --warn-missing-keys / --warn-missing-ids so a
catalog can lag en.json. That was also downgrading an empty value to a
warning, so a "" placeholder left in a catalog would merge. An absent key
is work in progress; an empty one is a leftover placeholder, and now always
fails.

* Check each webapp catalog's formatting

A hand edit that reindents or reorders a catalog buries its one real change
in a diff of thousands of lines, and nothing caught it. check_icu.mjs now
requires each catalog to be its own canonical serialization: keys in
code-point order, which every webapp catalog already has, a 2-space indent
and a trailing newline. A duplicate key fails too, since JSON.parse keeps
only the last one.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Check each server catalog's formatting

A hand edit that reindents a catalog buries its one real change in a diff
of thousands of lines, and nothing caught it. mmgotool i18n verify now
requires each catalog to be its own canonical serialization: a 2-space
indent and a trailing newline. The server catalogs have no order, so entry
order is left alone. A duplicate id fails, which the loader silently
collapses.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Warn about horizontal arrows in right-to-left locales

Arrows are not bidi-mirrored, so in a sentence read right to left an arrow
meaning "next" or "leads to" points backwards, and nothing checked for it.
check_icu.mjs now warns when a right-to-left locale's translation contains
a horizontal arrow or pointer. The locale's direction comes from Intl, so
no locale is listed by hand. Vertical arrows and the ↵ key symbol read the
same either way and are not flagged.

* Warn about horizontal arrows in right-to-left server catalogs too

check_icu.mjs warns when a right-to-left locale uses an arrow that is not
bidi-mirrored. mmgotool i18n verify now does as well, for single and plural
translations. It reports the one such string fa.json ships today.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Stop using -- as a dash in the checkers' comments

Both checkers take --flags, and a -- dash beside them reads as one.

* Move check_icu.mjs's per-message rules into icu_rules.mjs

check_icu.mjs held the file-level checks and the per-message ICU rules in
one script, so nothing else could run exactly the rules CI runs. The
per-message rules now live in icu_rules.mjs: analyzeSource parses an
en.json message once, and checkMessage returns each finding with a stable
rule id and a severity. check_icu.mjs keeps the file-level checks and
reports each finding as before.

The output over the shipped catalogs is byte-identical before and after,
with and without --warn-missing-keys. The i18n CI workflow now also runs
when icu_rules.mjs changes.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Warn about plurals missing a form their locale needs

A plural that shows its count but lacks a form its locale needs for some
integer renders that count with "other": Russian ships "5 каналы" where it
should say "5 каналов". check_icu.mjs now warns about it.

Explicit =N arms count as covering N. Forms that only fractions or numbers
in the millions select are not required. A source plural that itself
leaves out a form English needs is not held against the translations that
copy it. When two plurals on one variable fail the same way, the finding is
reported once.

It is a warning because 370 shipped strings have the problem, nearly all
in pl, cs, uk, ru, lt and ro.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Show "this user" for one user in two zh-CN removal prompts

Chinese never selects the plural category one, so the one {这位用户}
arm was dead and a single user read as 这些用户 (these users). =1
selects exactly one.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

---------

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-20 15:28:29 -04:00
a0ce180676 Apply the current team theme on backstage pages (#38804)
* Apply the current team theme on integrations and custom emoji pages.

Backstage omitted app__body and hardcoded a light palette, so only shared buttons followed the user's theme.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update integrations cards with themed compass illustrations and a tinted backstage surface.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use a responsive grid for integrations cards and fix light-theme webhook icon contrast.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Replace OAuth integrations card icon with a themeable SVG seal.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use compass OAuth card illustrations with a fixed dark logo and outgoing arrow.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update _backstage.scss

* Fix backstage mobile layout cascade, content margins, and SVG lint.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix stylelint property order in backstage styles.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update the integrations page e2e selector for the grid layout.

The backstage integrations list no longer uses Bootstrap flex classes, so MM-T569 timed out looking for .integrations-list.d-flex.flex-wrap.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Use a live image for the webhook icon override e2e.

The mattermost.com marketing icon 404s, so the avatar falls back to the default icon and MM-T622 never sees the proxied src.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Align session attribute e2e with String types and scroll rows into view.

Text fields render as String, and a bottom-row dot menu was opening upward so Escape never dismissed it.

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>

* Revert session attribute e2e changes unrelated to the backstage theme.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use published Compass illustrations on the integration cards.

Import the webhook, slash command, and bot artwork through Illustration instead of copied SVGs, and target those card links by role in the e2e.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fixes for sectionnotice

---------

Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 12:40:35 -04:00
Jesse Hallam f292f95d5a ci(docs-preview): avoid spurious failures from superseded runs (#39123)
* ci(docs-preview): stop rebuilding on ready_for_review

* ci(docs-preview): don't report failure when a run is superseded
2026-10-20 13:11:32 -03:00
491e853f7b Monthly maintenance (2026-10): Update server dependencies (#39220)
* Update server Go dependencies for October 2026.

Bump module dependencies within their current major versions, including golang.org/x/net v0.61.0 which fixes the HTTP/2 issues addressed in v0.60.0.

Co-authored-by: jesse <jesse@mattermost.com>

* Retrigger Server CI after Postgres shard runner kills.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Hold back logr v2.0.22 and azblob v1.8.0.

logr v2.0.23 defaults to a sync.Map level cache and pre-fills 65535 entries on every clear, which OOMs fully parallel app/api4 tests. azblob v1.8.1+ pulls apache/arrow-go into every filestore binary.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Upgrade azblob to v1.8.2.

Keep list-blobs on the SDK default format. The module links arrow-go for the opt-in Arrow parser.

Co-authored-by: jesse <jesse@mattermost.com>

* ci: make e2e dispatch-run job names unique per chain

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: jesse <jesse@mattermost.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-10-20 13:10:25 -03:00
5f4342d7f8 drip: AppError -> error: delivery tracking config (#39285)
Return error from SaveDeliveryTrackingConfig so its caller no longer
converts validateTrackedChannelIDs through model.AsAppError.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: jesse <jesse@mattermost.com>
2026-10-20 11:17:45 -03:00
Maria A NunezandCursor b0ddc5a13a Add Conflicts Babysit workflow to dispatch conflicting PRs to a Cursor agent (#39272)
* Add Conflicts Babysit workflow to dispatch conflicting labeled PRs to Cursor

On every push to master, find open PRs labeled AI/ConflictsBabysit whose
mergeable state is CONFLICTING (retrying briefly while GitHub reports
UNKNOWN) and POST each one to the Cursor automation webhook.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Remove workflow_dispatch trigger from Conflicts Babysit

Manual dispatch can target any branch, which would run that branch's
version of the script with the webhook secrets.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Make Conflicts Babysit best-effort so it never fails master CI

Webhook and PR search failures are reported as warnings and in the job
summary instead of failing the run; checkout and dispatch steps are
continue-on-error as a backstop.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Report Conflicts Babysit failures hidden by continue-on-error

Skip dispatch when checkout fails, add a step that warns and writes the
job summary when checkout or dispatch fails, and write the job summary
when the PR search fails.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-20 09:32:50 -04:00
be207f8972 Stop forcing SizeAwareImage containers to display:block (#39216)
* Stop forcing SizeAwareImage containers to display:block

A loaded SizeAwareImage set `display: block` as an inline style on its
`.file-preview__button` container. Because an inline style beats every
stylesheet rule, inline Markdown images were torn out of the text flow:
the channel header, the header-change system message and any message
with two images next to each other wrapped after every image, hiding
the rest of the header.

Only set `display` inline for the hidden state and let CSS pick the
visible display mode. The surfaces that need block-level images
(`.image-loaded` attachments and MM Blocks) already say so in their
stylesheets, and `.file-preview__button` defaults to `inline-block`,
which is what the Markdown and channel-header rules were already
asking for.

Fixes: https://github.com/mattermost/mattermost/issues/39212

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Assert inline Markdown images share a row with surrounding text

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Clarify inline image e2e text-box coordinate space

Co-authored-by: mattermost-code <matty-code@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-20 13:36:25 +03:00
Andre Vasconcelos 4772545518 Bumping prepackaged Dataminr plugin version to v3.0.0 (#39291)
Automatic Merge
2026-10-20 13:31:49 +03:00
05aa23fcae Skip flaky TestGenerateFlaggedPostReportAttachmentsOmittedAudit (#39274)
Skipping while the root cause is investigated under MM-71235.

Tests-only change.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-20 12:59:52 +05:30
Ben Schumacher 5ab80bea86 [MM-71255] Deduplicate healthcheck rule helpers and add missing license gates (#39288)
Add shared Snapshot accessors (LeaderDiag, ConfigRoot, RegisteredUsers,
ClusterEnabled, Since, LicenseAllows) and rule combinators (Single, Gate,
Licensed and their node variants), replacing per-package copies.

Gate cluster, Elasticsearch, LDAP, SAML, job and read-replica rules on
their license feature, since the server ignores those settings without
it. The license is consulted only when a rule would not resolve anyway,
and packets that predate the license feature list keep their findings.
2026-10-20 09:29:49 +02:00
3cef243d18 [MM-71188] Fix slash command autocomplete splitting single and curly quoted values (#39235)
* MM-71188 End single and curly quoted autocomplete args where execution does

parseInputTextArgument only grouped words in straight double quotes, so
autocomplete for /secure-connection and /share-channel split a single or
curly quoted value at its first space and showed wrong or no suggestions
after it, even though parseNamedArgs executes the command correctly.

Built-in commands that execute through parseNamedArgs now opt in through
AutocompleteQuotedArgProvider, which ends a quoted value with the same
quoteStyles and quotedToken rules the execution tokenizer uses. Other
commands, including plugins that split on whitespace, keep the old
behavior so their autocomplete does not diverge from execution.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-71188 Test quoted value autocomplete for /secure-connection

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-71188 Cover input after quoted values and non opted-in built-ins

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-71188 Preserve separator whitespace after a closed autocomplete quote

After a closed quoted value, keep all existing spaces in parsed and
insert a space when the quote has no following separator so the next
flag completion is not glued on.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 0 answered, 8 resolved, 0 declined

* Address PR feedback: 0 answered, 1 resolved, 0 declined

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-20 05:39:09 +00:00
9b1c4fd558 [MM-71057] Health Dashboard PR18: rules for auth and identity (#38917)
* [MM-71057] Add health rules for auth and identity

Port the script's LDAP, SAML and session checks as rules in a new
rules/auth package, plus two probe rules over the leader's LDAP and SAML
connection tests. Each code emits one result per cycle: unknown when its
config is missing, resolved when its gate (LDAP, LDAP sync or SAML) is
closed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71057] Cover the auth rules in the packet fixtures

The fixture configs had no LdapSettings or SamlSettings, so every auth
rule read unknown. The standalone packet now models an upgraded install
without AD/LDAP or SAML; the HA packet uses SAML sign-in with AD/LDAP
sync only, with matching probe results in each node's diagnostics. The
mmctl --packet test packet gains the settings and user count the auth
rules read, so its expected output is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71057] Use shared unknown reasons, raise SAML_VERIFY_OFF to warning

The probe rules, LDAP_RECOMMENDED_AT_SCALE and LDAP_SYNC_DUTY_CYCLE now
report a missing source with the shared diagnostics, stats and jobs
reasons instead of per-code ids. Only the duty cycle's "no measurable
run" message stays per code.

With Verify off, the SAML response signature is not validated, so a
forged response is accepted; that warrants warning rather than info.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71057] Fold the auth probe evals into one helper

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71057] Skip auth rules for settings that have no effect

SAML_SIGNATURE_SHA1 now requires SignRequest, LDAP_SKIP_CERT requires
TLS or STARTTLS, SAML IdAttribute matching handles claim URIs, and
ldapEnabled no longer needs EnableSync when Enable is already true.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71057] Tone down the LDAP sync duty cycle message

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71057] Evaluate the LDAP and SAML rules on Cloud

Cloud admins can change these settings, unlike the cloud_restrictable
ones or the pages RestrictSystemAdmin hides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep the mmctl packet fixture under the AD/LDAP recommendation threshold

The fixture's 300 users made LDAP_RECOMMENDED_AT_SCALE fire, adding an
unexpected finding to the packet output tests.

* Address review on the auth health rules

- SAML_VERIFY_OFF is critical: with Verify off the SP skips signature
  validation, so a self-made response to the ACS URL signs in as anyone.
- SAML_METADATA_UNREACHABLE becomes SAML_PROBE_FAILED, gated only on
  SAML being enabled. MM-71043 changes the probe to check the IdP
  certificate and IdP URL that sign-in uses instead of IdpMetadataURL.
- LDAP_SYNC_DUTY_CYCLE only averages success and warning runs; running,
  canceled and failed runs end early and understate the load.
- SAML_ID_IS_EMAIL is info, since it fires on every default SAML
  install, and the remediation says accounts migrate at next sign-in.
- LDAP_RECOMMENDED_AT_SCALE resolves for GitLab, Google, Office 365 and
  OpenID too.
- SAML_ID_MUTABLE also matches upn, userprincipalname and name (Entra's
  UPN claim).
- LDAP_SYNC_INTERVAL_LOW is removed; LDAP_SYNC_DUTY_CYCLE measures the
  actual load for short intervals.

* Simplify the auth rule test helpers

Drop the single-use codes() helper, read LDAP_RECOMMENDED_AT_SCALE's
state directly, and reuse configSnapshot's sections in probeSnapshot and
gateSnapshot. Note why leaderProbe skips the probe section errors that
its siblings check.

* Tighten LDAP_RECOMMENDED_AT_SCALE and the probe rules

- Count active users, not registered ones, which include deactivated
  accounts.
- Sync-only LDAP no longer counts as SSO: it signs no one in, so those
  users still have local passwords.
- An enabled provider resolves the rule even if another provider's
  setting is missing from the config.
- The probe rules resolve only on OK. A disabled status while the gate
  is open means the leader never ran the probe, so it is unknown.
- assertResults fails on a code that is not in allRules instead of
  skipping it.
- The standalone fixture gains the GitLab, Google, Office 365 and OpenID
  sections a real packet always has.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 06:56:58 +02:00
d430bb294f SEC-11251 Add Playwright subpath deployment E2E testing (#38833)
* Add Playwright subpath deployment E2E testing

* Address CodeRabbit findings: subpath CI trigger gap, container cleanup scope, internal webhook URL

* Widen subpath CI trigger to cover the full deployment configuration

* Also trigger subpath E2E tests on reusable Playwright workflow changes

* Also trigger subpath E2E tests when the goto/baseURL fixture chokepoints change

* Fix defaultBootEnv() dropping subpath SiteURL on per-spec-file reset

ensureDefaultServerEnv() restarts the server with defaultBootEnv()
before each spec file's first test. defaultBootEnv() didn't know about
subpath mode, so it reset SiteURL back to the plain default and broke
every subpath spec after the first server restart.

Co-authored-by: saturnino <saturnino@mattermost.com>

* Fix baseURL on mid-test restart in subpath mode; widen CI path filter

restartMattermostContainerLocked() always reset baseURL to the direct
server URL, dropping the subpath prefix after any mid-suite restart
(e.g. via ensureFeatureFlag()). Keep it pointed through nginx when
subpathMode is set.

Also widen the SHOULD_RUN_SUBPATH file filter to cover util.ts and the
channels/system_console page objects, which subpath specs depend on.

Co-authored-by: saturnino <saturnino@mattermost.com>

* fix specs that broke when the server runs behind nginx at /subpath:

- Add toHavePath/toHaveHrefPath matchers that prepend the subpath, and lint against toHaveURL('/path'), which Playwright resolves against baseURL and so drops the subpath
- Route unprefixed API calls onto the subpath, point mmctl at the subpath-aware URL, and build permalinks from the site URL
- Auto-start nginx whenever PW_SERVER_DEPLOY=subpath
- Skip the OIDC/SAML error-page cases (signature mismatch under a subpath) and the MM-69979 post_height cases for the subpath project
- Wait for the phase2 permissions migration before the autotranslation scheme tests
- Fix the mobile schedule menu dimmed-area click when the dialog starts above the viewport, and pin the date separator rollover test to UTC

* keep the subpath SiteURL reachable from the server

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: saturnino <saturnino@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-10-20 04:37:38 +00:00
bbe3f71849 [MM-71216] Apply incoming AcknowledgedAt when receiving synced acknowledgements (#39240)
The shared channel receiver chose create or delete based on whether a
matching acknowledgement row existed, ignoring the incoming AcknowledgedAt.
A removed acknowledgement the receiver had no row for was created as active,
and an acknowledgement re-sent after a withdrawal was dropped because the
withdrawn row still exists with AcknowledgedAt == 0.

Apply the sender's state idempotently instead, as MM-71096 does for reactions.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-20 00:30:15 -04:00
9df984246f Render URL too long errors inline to stop the redirect loop (#38286)
A request that exceeds ServiceSettings.MaximumURLLength is rejected with a
414 and answered with a page that sends the browser to a signed /error URL.
When the limit is small enough, that generated URL is over the limit too, so
the browser bounces between freshly signed error URLs forever.

RenderWebError now serves the error inline whenever the error page URL would
itself be rejected, so the browser settles on a single stable page.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-19 21:46:26 +00:00
aeccde2d90 [MM-69961] Fix login being blocked for all users when Guest Access is disabled while Magic Link stays enabled (#38749)
* MM-69961: don't block all logins when guest access is disabled

The login page gated its POST /users/login/type pre-check on
EnableGuestMagicLink alone, while the server also requires guest accounts
to be enabled and licensed. With magic links on and Guest Access off the
endpoint answered 404 with an empty body under a JSON content type, so
the client's response.json() threw "Received invalid response from the
server.", the password field never rendered and every login method was
blocked.

Mirror the server's availability conditions in the login page so the
pre-check is only attempted when it can succeed, and have getLoginType
return a JSON error body instead of a bare 404. The nil license is now
guarded too; it previously panicked on an unlicensed server.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: close coverage gaps found by mutation testing

Mutation testing showed three production paths the new tests did not pin:
the server gate's EnableGuestMagicLink and license feature conditions, and
the redundant isLicensed term on the client. The client license only
carries GuestAccounts when a license is present, so that term is dropped
rather than tested.

Adds action-layer coverage for the response shapes getLoginType can now
return, since that is where MM-69961 actually surfaced.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: drive the login type pre-check over real HTTP in tests

The component tests mocked the getUserLoginType action, so they could
never reproduce the empty-404-under-application/json response that
actually broke login. Nock the endpoint instead and assert on the banner
the user sees, including the case the server fix removes. The regression
tests now prove the pre-check is skipped by leaving that response
unconsumed rather than by asserting on a mock.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: assert the message the login page renders, not just the error id

model.NewAppError falls back to using the id as the message, so a missing
en.json entry would have left the raw id on screen with every suite green.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: trim comments and match local error-variable naming

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: retrigger CI after Enterprise Postgres flake

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: retrigger CI after Postgres shard flake

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69961: match Playwright login error banners used on enterprise

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>
2026-10-19 17:12:14 -04:00
5c012a1184 [MM-61894] Keep markdown formatting when pasting text copied from a message (#38287)
* MM-61894: keep markdown when pasting a copied message

The paste handler only converted pasted HTML into markdown when it held a
table or a link, so copying a rendered message out of a channel and pasting
it dropped headings, lists, emphasis and code. It now converts any HTML that
carries formatting markdown can express, and Turndown knows how to read back
the markup Mattermost renders for code blocks, channel links and hashtags.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-61894: cover pasting a copied message with tests

Renders messages through the real Markdown component and pastes the resulting
markup back, so the tests exercise the markup a browser actually puts on the
clipboard.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-61894: lengthen code-block fences past inner backticks

When a copied Mattermost code block contains ```, a hardcoded three-backtick
fence would close early. Size the fence from the longest run in the code.

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>
2026-10-19 17:11:33 -04:00
06f070f339 [MM-70052] Fix group search returning nothing when the term starts with "@" (#38748)
* Fix group search ignoring results when the term starts with "@"

The group search queries matched the raw term against UserGroups.Name and
UserGroups.DisplayName, so a mention such as "@developers" never matched a
stored group name. The channel "Add people" dialog searches by mention, so
its request returned nothing and the dialog kept rendering the locally
cached group, which loses its member IDs once the User Groups list is
browsed. That left the "Add N people" shortcut with an empty tooltip and no
members to add.

Strip a single leading "@" when building the search pattern, matching how
user search and the webapp's own group filtering already treat mentions.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* chore: match max login attempts e2e to current login error banner

The Playwright full suite failed on max_login_attempts.spec.ts because it
asserted the old exact invalid-credentials and lockout strings. Use the
login page helper (which already matches both banner variants) and a
prefix check for the lock message.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Revert unrelated max login attempts e2e change

The login-banner assertion update was a pre-existing full-suite test
fix unrelated to the MM-70052 group-search fix. Removing it here to keep
this PR scoped; it can be addressed in its own change.

* chore: match max login attempts e2e to current login error banner

The full Playwright suite fails this spec on the old exact invalid-credentials
and lockout strings. Use the shared login-page helper (which already matches
both banner variants) and a prefix check for the lock message.

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>
2026-10-19 16:49:02 -04:00
b4484b200a [MM-71061] Health Dashboard PR24: rules for compliance and data governance (#38915)
* [MM-71061] Add compliance and data governance health rules

COMPLIANCE_NO_DIR, AUDIT_LOG_OFF, RETENTION_TIGHT_BATCHES and
EXPORT_GR_TIMEOUT_LOW, each gated on the feature it lints so a disabled
feature resolves. AUDIT_LOG_OFF follows the server's IsAuditLoggingActive,
so an advanced logging target bound to an audit level counts as a sink and
audit-delivery alone does not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71061] Cover the compliance rules in the packet fixtures

Add the audit, compliance, retention and message export settings to the
golden packet configs so the new rules evaluate offline: standalone fires
COMPLIANCE_NO_DIR, ha fires EXPORT_GR_TIMEOUT_LOW. The mmctl packet stub
gets the same settings with resolving values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71061] Fire a compliance rule in the live/offline parity test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71061] Use the shared stats reason in AUDIT_LOG_OFF

Replace health.rule.audit_log_off.message.stats_unavailable with
healthcheck.ReasonStatsUnavailable from the base branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71061] Reuse config.IsAuditLoggingActive in AUDIT_LOG_OFF

Drop the local copy of the audit sink check and its tests, short-circuit
retentionEnabled once message deletion is on, and list the new codes in
TestBuiltinRegistry instead of a per-package registration test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71061] Honor the AdvancedLogging license in AUDIT_LOG_OFF

The server ignores advanced audit targets on a license without
AdvancedLogging, so they no longer count as a sink there. Also reuse the
model default as the retention batch pause threshold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71061] Read the AdvancedLogging license from leader diagnostics

Packets now carry the license features, so AUDIT_LOG_OFF only allows
advanced audit targets unconditionally when the license is unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71061] Share the sibling rule test helpers in the compliance tests

Use the want/configSnapshot/withUsers/assertResult set from the platform
and cluster packages so results are checked on all details and value,
and drop two unused subjects from the packet reader test.

* [MM-71061] Count custom retention policies in RETENTION_TIGHT_BATCHES

Custom team and channel policies run the data retention job with the
same batch pause even when both global deletion flags are off. The
packet has no policy count, so treat a data retention job created
within 48 hours of collection as the job running.

* [MM-71061] Lower compliance rules to info and gate AUDIT_LOG_OFF on Enterprise

AUDIT_LOG_OFF, RETENTION_TIGHT_BATCHES and EXPORT_GR_TIMEOUT_LOW flag
configuration choices rather than faults, so they report as info.

AUDIT_LOG_OFF now only evaluates on Enterprise tier and above, matching
the System Console, which hides the Audit Logging page below that tier.

* [MM-71061] Add SupportPacketLicense.MinimumEnterprise for the AUDIT_LOG_OFF gate

* [MM-71061] List one rule code per line in TestBuiltinRegistry

* Fix shadowed ok in evalAuditLogOff

* [MM-71061] Gate compliance rules on their license features

COMPLIANCE_NO_DIR, RETENTION_TIGHT_BATCHES and EXPORT_GR_TIMEOUT_LOW
now require the Compliance, DataRetention and MessageExport features
their runtimes check, so stale settings on an unlicensed or downgraded
server no longer fire. Unlicensed servers resolve; licensed packets
without feature data return unknown.

* [MM-71061] Resolve unlicensed servers in LicenseFeature

Snapshot.LicenseFeature now reports a feature as off for an unlicensed
server, which carries no features, and keeps unknown for licensed
packets that predate them. The compliance rules wrap their Eval in
requireFeature instead of repeating the gate, and the tests drop the
cases the shared license/config test already covers.

* Add compliance license features to mmctl health test packet

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 20:56:19 +02:00
Scott Bishel e7e0ee99d2 Docs: interactive dialog checkbox elements and per-channel join/leave messages (#39260)
* update documentation for checkbox group/matrix and join/leave channel settings

* update docs from coderabbit review
2026-10-19 11:43:43 -07:00
Jesse Hallam 70a7b86bb1 Resolve Jest src imports from rootDir only (#39150) 2026-10-19 23:23:25 +05:30
Jesse Hallam 57ea425e5a Fix existing translation errors and add glossary terms (#39207)
* Fix "Updating..." strings translated as "Uploading..."

ja, ko and pt-BR rendered several "Updating..." labels as "Uploading...",
and bg used the ungrammatical "Актуализира ...". Match the sibling
*.updating strings each locale already translates correctly.

* Fix Swedish "emoij" misspelling

Spell it "emoji" (definite "emojin") across the webapp and server
catalogs, as the sv glossary requires, and translate the custom emoji
delete confirmation title, which read "Enable custom emoji".

* Retranslate stale "Attribute Management" link label

The English label changed from "User Attributes" to "Attribute
Management", but ten locales still shipped the old meaning. Reuse each
locale's existing translation of the Attribute Management section title.

* Fix Hosted Push Notification Service name in de and ru

ru rendered it as Mattermost's "own" push service, and de garbled the
name into "Hosted Push Notifications Dienste".

* Translate "orphaned field" as an attribute of an uninstalled plugin

Locales rendered the label as a literal "orphaned field", "field
without an owner" or "attribute of a removed plugin". The button deletes
a user attribute whose owning plugin is no longer installed, which is
how the neighbouring strings already describe it, so say that, using
each locale's word for attribute. Spell this out in the en.json
description for future passes.

* Use the glossary term 用户组 for user groups in zh-CN

zh-CN rendered user groups (synced AD/LDAP groups, custom groups, group
mentions) as 群组, which it also uses for group messages. Switch the
user-group sense to the glossary target 用户组, leaving group messages
and group channels as 群组.

* Add five glossary terms from the health dashboard and push settings

health dashboard, health finding, Agents plugin, Hosted Push Notification
Service and PushNotificationServer appeared in new strings with no agreed
rendering. Targets follow each locale's majority rendering, with notes
recording the catalog strings that deviate.

* Render Hosted Push Notification Service per the glossary

The push terms checkbox and help text garbled, truncated or dropped the
service name in most locales (vi "Hosted Hosted", ro "Împinge", it with
no name at all), and cs, zh-CN and zh-TW called the hosted service TPNS.
Use each locale's new glossary target.

* Point the TPNS help text at the Test Push Notification Service

The help under the TPNS option told admins to learn about the Hosted
Push Notification Service, copied from the HPNS help. Name the test
service and rename its link tag to linkTPNS to match; the docs link is
unchanged since one page covers both services.

* Spell Romanian "pluginul" without a hyphen

The ro catalogs mixed "plugin-ul" with "pluginul". Romanian hyphenates
the article only when a loanword's final letter is not pronounced as
written, so use "pluginul", "pluginuri" and so on throughout, and correct
the glossary note that asked for the hyphen.

* Keep push service names in English in the glossary

Hosted Push Notification Service and Test Push Notification Service are
named Mattermost services, so mark both doNotTranslate, as for Calls and
Playbooks, and add the missing TPNS entry. Locale notes say how to frame
the name grammatically.

* Retranslate push service help and names per the glossary

Retranslate the TPNS help text from its corrected English, keep the
service names in English where catalogs had translated them, and fix
help text that was half English or garbled (ro, vi, lt).

* Use neuter forms of "plugin" in Swedish

The sv glossary treats plugin as neuter (ett plugin), but many strings
used common-gender forms such as "pluginen", "Agents-pluginen" and "en
plugin". Use "pluginet", "ett plugin" and neuter agreement throughout.

* Fix "uptime SLA" in the Swedish HPNS option

"senast SLA" read as "latest SLA".

* Fix zh-CN team group sync description saying adding a user removes them
2026-10-19 17:08:17 +00:00
2de97aee85 [MM-69775] Delete the server-side draft when sending from the Drafts panel (#38861)
* MM-69775: Delete the server draft when sending from the Drafts panel

Sending a draft from the Drafts panel only cleared the locally stored
copy: createPost dispatches storeDraft(null), which is a setGlobalItem
and never reaches Client4.deleteDraft. The server draft survived and
getDrafts restored it on the next team switch or reload.

The channel composer is unaffected because its handleDraftChange sees
the emptied draft after submit and dispatches removeDraft, which does
delete the server copy.

afterSubmit now calls handleOnDelete for a plain send as well as for a
scheduled one, leaving the scheduled-post send path untouched.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69775: Cover the Drafts panel send path with real submit wiring

draft_row.test.tsx mocked useSubmit outright, so nothing exercised what
DraftRow does after a post is created. The mock is gone and the action
rows are now interactive fakes that invoke the real callbacks, so the
tests run through useSubmit, onSubmit, createPost and afterSubmit and
assert on the requests Client4 actually receives.

Covers a plain channel send, a thread send, scheduling, sending a
scheduled post, and both failure paths.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69775: Add E2E coverage for sending a draft from the Drafts panel

Both specs assert the server draft list is empty after the send, which
is the invariant the bug broke; the UI assertions alone could not see it
for the thread case. Reverting the draft_row fix fails both specs on
that assertion.

DraftsPage gains the Send message now modal and a sendDraft helper,
mirroring ScheduledPostsPage.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69775: Drop the now-dead isBeingScheduled ref and sharpen the tests

afterSubmit no longer branches on isBeingScheduled, which left the ref
written in two places and read in none, so it is gone.

Test changes come from a review pass that found three assertions which
passed with the fix reverted:
- the drafts-list assertion is now driven by a fake server draft table
  that deleteDraft mutates and getUserDrafts serves back, with a real
  getDrafts refetch, so it reproduces the reported symptom
- the create-failure test became a pending-promise test, since a
  rejected createPost never reaches afterSubmit; it now pins that the
  delete waits for the created post instead of firing on click
- the scheduled-post test also asserts the live channel draft survives,
  which kills a keepDraft flip, and sends twice
- the Playwright thread test dropped its post-reload assertions, which
  could not fail for this bug, and says what it actually proves

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69775: Assert the scheduled draft leaves the panel, not just the server

Scheduling returns before createPost, so handleOnDelete is the only
thing that clears the local copy on that path. Asserting only on
deleteDraft's arguments let a wrong storage key survive: the server
delete still looked right while the row stayed in the Drafts panel.

Also assert the fake server draft table itself shrank after a plain
send, so a degraded fixture cannot quietly weaken that test.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* MM-69775: Tighten comments across the change

Drops comments that restated the assertion on the following line, keeps
the ones carrying facts the code cannot show, and matches the comment
style of each surrounding file.

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>
2026-10-19 12:51:54 +00:00
Amy BlaisandClaude Opus 5 640b099b64 Update minimum supported Firefox version (#39253)
* Update minimum supported Firefox version

* Update minimum supported Firefox version in the requirements docs

Matches the i18n change in this PR: the PC web browser support table now
lists Firefox v153+ for both self-hosted and Cloud.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:45:24 +00:00
b25056d4ea drip: AppError -> error: delivery tracking (#39171)
* drip: AppError -> error: delivery tracking

Return error from validateTrackedChannelIDs so callers no longer receive
a concrete *model.AppError that can collapse into a typed nil.

Co-authored-by: jesse <jesse@mattermost.com>

* Add model.AsAppError and Context.SetError for error-to-AppError boundaries

* Use model.AsAppError in SaveDeliveryTrackingConfig

* Use model.AsAppError in TestLdapConnection and TestLdapDiagnostics

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: jesse <jesse@mattermost.com>
2026-10-19 09:29:30 -03:00
2657f70938 [MM-71062] Health Dashboard PR26: rules for license and seats (#38916)
* [MM-71062] Add license seat and expiry snapshot accessors

LicenseSeats and LicenseExpiresAt read the leader's license summary, so
license rules give the same answer offline as live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71062] Add health rules for license and adoption

Ten rules in a new license area package: license expiry, trial license,
seat utilization and the enforced seat limit, monthly engagement, and
how much of Mattermost's integration surface is in use. Seats are
counted the way the server counts them, and expiry is measured against
the snapshot's collection time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71062] Report why license and adoption rules are unknown

Unknown results now carry the shared reason for the missing source
(license, stats, plugins, collection time) instead of an empty id.
SEATS_LOW_ENGAGEMENT keeps its own reason for a server with no active
users, which no shared reason describes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71062] Simplify license rules

Reuse seatUtilization in the seat limit rule, use License.IsTrialLicense,
and trim comments.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71062] Count single-channel guests as seats when guest accounts are disabled

Mirror GetServerLimits, which only exempts single-channel guests while
guest accounts are enabled. Keep the trial rule on the raw IsTrial flag
so live and Support Packet results agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71062] Describe seats as counting toward the license limit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71062] Evaluate the engagement and workflow usage rules on Cloud

Cloud admins can change these settings, unlike the cloud_restrictable
ones or the pages RestrictSystemAdmin hides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71062] Read the license rules' inputs from leader diagnostics

Snapshot.License is now an accessor over the Support Packet license
summary, so the rules and their tests read it from the leader node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71062] Fix shadowed ok in seatsUsed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71062] Align the latest version in the packet-without-expiry parity check

* [MM-71062] Remove code text from engagement subtest names

* [MM-71062] Record the canonical trial status in the Support Packet

Use License.IsTrialLicense() so trials recognized only by their duration are reported as trials.

* [MM-71062] Describe seats as counted users and qualify license removal

The seat messages count users who take a seat, not active users. The server never removes an expired Mattermost Entry license, so the removal consequence moves from the remediation into the message, with an Entry variant that omits it.

* [MM-71062] Drop the trial, utilization, engagement and workflow rules

LICENSE_TRIAL duplicated LICENSE_EXPIRING for the whole trial; the trial
status now selects a trial variant of the expiry messages instead.
SEATS_LOW_UTILIZATION, SEATS_LOW_ENGAGEMENT and the WORKFLOW_USAGE codes
are account-management signals, not problems an admin can fix.

* [MM-71062] Move the seat rule tests to seats_test.go

* [MM-71062] Simplify the seat rule helpers

* [MM-71062] Add SupportPacketLicense.IsMattermostEntry

* Drop the unreachable Entry message from LICENSE_EXPIRED

LoadLicense rebuilds the Entry license in memory with a fresh 100-day
expiry every time it runs, so an Entry snapshot can never reach either
expiry rule.

* Resolve both license expiry rules for Mattermost Entry

The server never expires an Entry license, so neither rule should fire
for one, matching doLicenseExpirationCheck.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 12:27:33 +00:00
f79ba36eed Skip flaky TestDoSetupContentFlaggingProperties (MM-71223) (#39249)
* Skip flaky TestDoSetupContentFlaggingProperties

Skipping while the root cause is investigated under MM-71223.

Tests-only change.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Retrigger CodeRabbit review

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>
2026-10-19 09:26:12 -03:00
7df5e30433 [MM-71059] Health Dashboard PR21: rules for jobs (#38914)
* [MM-71059] Add health rules for stuck, wedged and failed jobs

JOB_STUCK, JOB_WEDGED_AT_ZERO and JOB_FAILED report one result per
collected job type. Each type is gated on the setting that runs its
worker, and ages are measured from the snapshot's collection time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71059] Cover the job rules in the packet fixtures

The fixtures gain the job gate settings and realistic job runs, so the
standalone packet reports a failed LDAP sync and the HA packet a stuck
message export. The mmctl test packet gets the settings and jobs file
it needs to stay fully evaluated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71059] Exercise the job rules in the live/offline parity test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71059] Use the shared reasons for missing jobs data

The job rules' unknown results for missing job history and an unknown
collection time now use ReasonJobsUnavailable and
ReasonCollectedAtUnknown, so their five per-code ids are gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71059] Tidy the job rule tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71059] Look past pending runs queued behind a stuck job

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71059] Report unknown when queued runs fill the collected job history

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71059] Skip canceled runs when checking for a failed job

A worker stop on restart or upgrade cancels the running job, so a
canceled run after a failure cleared JOB_FAILED without any run having
succeeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71059] Fire JOB_WEDGED_AT_ZERO on idle time instead of runtime

Progress is a whole percentage for search indexing and message export,
so a large job that is working still reads 0 for hours and fired the
rule after 6h. Every batch updates LastActivityAt, so requiring an hour
without activity only catches jobs that never got going, and catches
them sooner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71059] Resolve old data retention failures once retention is off

With both global deletion settings off and no custom policy, the
scheduler stops creating data retention runs, so a failed run stayed
the latest one and JOB_FAILED fired forever. Custom policies are not in
the snapshot, but an active one would have produced a newer run within
a week.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 13:39:01 +02:00
LevandClaude Fable 5.1 a21d6019b5 Add a hub channel broadcast benchmark (#38991)
* Add a hub channel broadcast benchmark

BenchmarkHubChannelBroadcast measures delivering one channel-scoped
posted event through the production WebSocket hubs (one per CPU): publish
with PublishSkipClusterSend, then wait until every member connection has
received it. There are 5000 connections, each user a member of 200
channels drawn from a pool of 20000; 50 or all 5000 of them belong to the
event's channel. Cases cover EnableWebHubChannelIteration off and on,
plus a churn case (on, 50 members) that invalidates a random user's
channel membership every millisecond while membership loads take a
millisecond, as a primary database round trip would.

Membership loads are served from an in-memory store stub that returns
freshly allocated strings on every load, like a database scan, so the
benchmark needs no database. An untimed warm-up fills the per-connection
membership caches as the first event does in production.

Besides time and allocations per event, each case reports:
- live-MB, live-kobjs: heap and objects held once connections have loaded
  their memberships
- gc-ms: wall time of a full collection over that heap
- p99-us: 99th percentile latency of one event

Each case drops its connections and stops its hubs, so every case starts
from the same heap.

There is no benchmark at this level today (the existing hub benchmarks
cover the connection index only). It uses only existing APIs, so it runs
unchanged before and after changes to the broadcast path and gives those
changes a common, reproducible measurement.

Release Note:
NONE

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the nearest-rank index for the benchmark's p99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Read the benchmark's base heap after the store is filled

The mock channel store's memberships (5000 slices of 200 IDs, plus the
ID pool) were allocated after the base reading, so the live heap the
benchmark reports counted them: about 17 MB that belongs to the fixture,
not to the hubs or connections. The store is now filled first, then the
base is read, then the platform starts and the connections register.
Same for every case, so earlier comparisons hold; the absolute live-heap
figures are lower by the fixture's size.

Release Note:
NONE

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:38:31 +00:00
Ben SchumacherandClaude Opus 5.5 acb011f1ea [MM-71100] Health Dashboard PR25: rules for platform, filestore, logging and plugins (#39020)
* [MM-71100] Health Dashboard PR25: rules for platform, filestore, logging and plugins

Adds the rules/platform package with 14 codes: file storage (local storage
in a cluster, S3 bucket and SSL, the connection test), logging, metrics,
plugins (framework, health check, enabled plugins that are not running),
the server OS, privileged listen ports, and per-node file descriptor use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71100] Health Dashboard: update packet fixtures and finding expectations for the platform rules

The fixture configs gain the file, log, metrics and plugin settings a real
packet carries. The parity test ignores the file descriptor ratio, which
each collection samples separately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71100] Health Dashboard: reuse shared test helpers in the file descriptor rule tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71100] Health Dashboard: resolve the cluster file storage rules before reading the driver

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71100] Health Dashboard: resolve METRICS_OFF when the license lacks the Metrics feature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71100] Health Dashboard: merge the cluster local file storage rules and check file storage on every node

FILESTORE_LOCAL_IN_CLUSTER now covers relative and absolute directories as a single warning, since the HA guide's ./data/ on an NFS mount is a valid shared setup. FILESTORE_UNREACHABLE evaluates each node's connection test, and BIND_PRIVILEGED_PORT says the warning stays after granting the capability through systemd.

* [MM-71100] Health Dashboard: lower LOG_FILE_OFF to info

The message already says file logging off is fine when an external collector captures the logs, so a warning overstates it.

* [MM-71100] Health Dashboard: move LOG_DEBUG_PROD and PLUGINS_OFF to internal, reword LOG_FILE_OFF

LOG_DEBUG_PROD drops to info. LOG_FILE_OFF now explains that without a
log file the Support Packet carries no server logs, even when an external
collector captures stdout.

* [MM-71100] Health Dashboard: lower ENV_UNSUPPORTED_OS to warning

* [MM-71100] Health Dashboard: simplify platform rules and their tests

* [MM-71100] Health Dashboard: enable metrics in the live/offline parity test

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 12:17:57 +02:00
Amy BlaisandClaude Opus 5 c77a59ea33 Add dot release docs for server v11.11.2, v11.10.3 and v11.7.13 (ESR) (#39004)
* Add dot release docs for server v11.11.2, v11.10.3 and v11.7.12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DR6hsSCuRfXqAnyHNY7sKo

* Update mattermost-v11-changelog.mdx

* Move v11.7.12 docs out to their own PR

The v11.7.12 (ESR) content from this PR now lives in its own branch and
PR so it can land on its own schedule, independently of v11.11.2 and
v11.10.3.

Removed here, unchanged there:
 - deploy-rhel.mdx / deploy-tar.mdx: Current ESR back to 11.7.11
 - mattermost-server-releases.mdx: v11.7 row back to 11.7.11
 - mattermost-v11-changelog.mdx: 11.7.12 entry removed
 - version-archive.mdx: v11.7 Enterprise and Team entries back to
   11.7.11, including their original SHA-256 checksums

This PR now covers v11.11.2 and v11.10.3 only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Update mattermost-v11-changelog.mdx

* Refresh v11.11.2/v11.10.3 notes and add v11.7.13 dot release docs

Adds the release notes merged to release-11.11 and release-11.10 after
this PR was drafted, backfills three entries that shipped in v11.7.12 but
were missed in its changelog entry, and documents the v11.7.13 ESR dot
release across the five dot-release pages.

11.11.2: Jira v4.8.2, EXIF orientation read (#38739), connection string
reporting (#38644), received_group WebSocket scope (#38299), PDF
content-stream array parsing (#38781).

11.10.3: EXIF orientation read (#38739), connection string reporting
(#38644), PDF content-stream array parsing (#38781).

11.7.12: EXIF orientation read (#38739), PDF content-stream array
parsing (#38781), signed-in user's theme on the full-page error screen
(#38812). All three are in the v11.7.12 tag but were merged after the
entry was written.

11.7.13: new entry for Jira v4.7.4 and the commits in release-11.7 since
the v11.7.12 tag, plus the ESR version bump in the deployment guides,
the server releases table and the version archive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Update mattermost-v11-changelog.mdx

* Add GitLab plugin v1.13.1 to the v11.11.2 and v11.10.3 notes

Picks up the prepackaged GitLab bump merged to release-11.11 (#39139) and
release-11.10 (#39138) after the last changelog refresh. No new commits on
release-11.7 since v11.7.13 was written up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Add the Enable File Search setting to the v11.7.13 notes

Picks up #39059, cherry-picked to release-11.7 as #39148 after the last
changelog refresh. No new commits on release-11.11 or release-11.10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Update mattermost-v11-changelog.mdx

* Add Elasticsearch file indexing setting and move dot releases to Oct 19

Adds the ElasticsearchSettings.EnableFileIndexing bullet to the v11.7.13
entry (server #39058, cherry-picked to release-11.7 as #39147), and moves
the v11.11.2, v11.10.3, and v11.7.13 release dates to 2026-10-19.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Add OpenSearch indexing fixes to the v11.7.13 release notes

Covers server #39151 (MM-70268), cherry-picked to release-11.7 as #39244:
the stalled OpenSearch bulk indexing timeout and the nil-pointer panic in
the shared search logger on failed node discovery.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nrmq4ezefYWqa9YkxtX3mR

* Update version-archive.mdx

* Update version-archive.mdx

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:43:17 +03:00
08cf1105ab [MM-71060] Health Dashboard PR23: rules for notifications and messaging (#38912)
* [MM-71060] Add the ConfigInt64 health snapshot accessor

TeamSettings.MaxNotificationsPerChannel is the first rule input stored as
*int64, so rules need an int64 counterpart to ConfigInt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71060] Add notification and messaging health rules

Adds PUSH_ID_ONLY, LINK_PREVIEWS_DISABLED, MAX_NOTIFICATIONS_PER_CHANNEL,
TYPING_MESSAGES_AT_SCALE and SMTP_UNREACHABLE to the notifications area,
with their English text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71060] Update packet and parity expectations for the messaging rules

The packet fixtures gain the default values of the settings the new rules
read, so offline evaluation resolves them instead of reporting unknown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71060] Scope the push round-trip test to the push server codes

PUSH_ID_ONLY fires under the test server's default push contents.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71060] Use shared unknown reasons and keep parity off SMTP

TYPING_MESSAGES_AT_SCALE and SMTP_UNREACHABLE now report the shared
ReasonStatsUnavailable and ReasonDiagnosticsUnavailable instead of
their own ids. The live/offline parity test turns email notifications
off so the two collections never probe SMTP separately.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71060] Simplify the SMTP rule guard and push test filtering

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71060] Share one generic body across the config accessors

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71060] Check the SMTP leader explicitly and assert value checks

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71060] Evaluate the push contents and link preview rules on Cloud

Cloud admins can change these settings, unlike the cloud_restrictable
ones or the pages RestrictSystemAdmin hides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71060] Note that configValue can become a generic method on Go 1.27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71060] Only flag full push contents when the license offers id_loaded

full is the server default, and without the ID-only push notifications
feature the server falls back to generic, so the finding is only
actionable when the license includes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop LINK_PREVIEWS_DISABLED rule

It only reports a setting an admin deliberately turned off, so it adds noise without a useful signal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop TYPING_MESSAGES_AT_SCALE rule

Typing events are already bounded by MaxNotificationsPerChannel, the
client throttle, and server-side delivery only to viewers of the channel,
so total account count is a poor proxy for typing load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Clarify push and channel-mention health rule text

PUSH_ID_ONLY now states the device reachability cost of id_loaded and
when to mute. MAX_NOTIFICATIONS_PER_CHANNEL now matches its 5000
threshold and states that lowering the limit disables channel-wide
mentions in larger channels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71060] Probe SMTP for invitations and verification, fix mention limit wording

Invitation and verification emails go through SMTP even when notification
emails are off, so the Support Packet probe and SMTP_UNREACHABLE now run
when any of those is enabled.

Channel-wide mentions are blocked once a channel reaches
MaxNotificationsPerChannel, not only above it; the finding now shows the
configured limit instead of an off-by-one member count.

* [MM-71060] Set the SMTP-gating settings in the mmctl test packet

The SMTP rule now reads RequireEmailVerification and EnableEmailInvitations, so a packet missing them reports the rule as not evaluated.

* [MM-71060] Probe SMTP when password sign-in is enabled

Local password users can request a reset email even with notification,
verification and invitation emails off, so SMTP_UNREACHABLE and the
Support Packet probe now also run when email or username sign-in is on.

* [MM-71060] Reduce merge conflicts in health rule test fixtures

Expected finding lists are sorted by code and the mmctl test packet
config is built from defaults, so parallel rule PRs stop editing the
same lines.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 10:35:21 +02:00
Ben SchumacherandClaude Opus 5.5 353f76fe30 [MM-71101] Health Dashboard PR19: rules for database (#39021)
* [MM-71101] Add ConfigStrings snapshot accessor

ConfigStrings reads list settings such as DataSourceReplicas. It returns
ok=false when the config section is absent or any element is redacted, so a
redacted replica DSN cannot be read as a host name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71101] Add database health rules

Ports the database checks from the health check script into a new
rules/database package: replica sizing, Trace, MaxOpenConns,
QueryTimeout, HA failover connection bursts (merged with the pooler
check), PostgreSQL version, pool saturation, unused replicas, IP-literal
DSN hosts, replica lag monitoring and MaxIdleConns above MaxOpenConns.

Rules that read DSNs only report derived facts; no DSN or host leaves
the rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71101] Update health packet fixtures and expectations for database rules

The fixture configs gain the SqlSettings and ElasticsearchSettings keys a
real packet carries, and the HA fixture raises MaxOpenConns to 200 so the
golden set covers DB_HA_FAILOVER_STORM_RISK offline. The mmctl test packet
gains the database diagnostics and stats the rules read, and the parity
test expects DB_QUERY_TIMEOUT_HIGH from the test store's 60 s timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71101] Parse every DSN host and skip non-Postgres version checks

hostsFromDSN replaces hostFromDSN and returns every host a DSN names:
comma-separated host lists, host/hostaddr query parameters in URL form,
and hostaddr in keyword form. DB_CONNECTION_IS_IP fires when any host is
an IP; the failover rule treats a DSN as pooled when any host is a pooler.

DB_POSTGRES_UNSUPPORTED returns unknown when the diagnostics report a
database type other than Postgres, so older MySQL packets no longer
raise a Postgres version warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Raise DB_NO_SEARCH_REPLICA threshold to 2M posts

Matches the documented point where database search starts to degrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove DB_CONNECTION_IS_IP rule

An IP address in a DSN is correct for loopback, floating VIPs and k8s
services, and harmless on single-node installs, so the rule was mostly
noise. hostsFromDSN stays for the failover rule's pooler check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove DB_REPLICA_LAG_UNMONITORED rule

ReplicaLagSettings only feeds Prometheus gauges, and managed databases
already report replica lag, so the rule was an observability nudge with
nothing misconfigured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Simplify database health rules and tighten failover storm risk

- Drop Snapshot.ConfigStrings; its only caller now just counts entries.
- Parse only URL-form DSNs: sanitized config redacts every other form.
- DB_HA_FAILOVER_STORM_RISK counts only primary pools in the burst and
  treats loopback hosts and Unix sockets as a per-node pooler.
- DB_POOL_SATURATED checks the unlimited-pool gate before reading
  diagnostics.
- Share the minimum PostgreSQL version via model so sqlstore and
  DB_POSTGRES_UNSUPPORTED cannot drift apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fire DB_HA_FAILOVER_STORM_RISK when any host bypasses the pooler

The driver tries a host list in order, so a direct host in the list can
take the full reconnection burst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Skip DB_NO_SEARCH_REPLICA when database search is disabled

DB_POSTGRES_UNSUPPORTED now also reports unknown when the database type is missing.

* Base DB_REPLICA_UNUSED on replica online state

Open connection counts drop to zero on idle replica pools, so the rule now reads a new replicas_online support packet diagnostic.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 09:44:44 +02:00
f491fb9299 [MM-71058] Health Dashboard PR20: rules for search (#38913)
* [MM-71058] Add the search health rules

Ports the Elasticsearch / OpenSearch checks: localhost ConnectionURL in a
cluster, a failed connection test, post-count scale thresholds, live
indexing batch size, unsupported major version, missing analysis-icu
plugin, and skipped TLS verification.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71058] Cover the search rules in the packet fixtures

The fixtures had no ElasticsearchSettings or search diagnostics, so the
search rules could only report unknown offline. Standalone now runs with
indexing off; HA runs OpenSearch with an upgraded LiveIndexingBatchSize
of 1. The mmctl test packet gets the search data every real packet has.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71058] Use the shared reasons in the search rules

The post count and the leader's search diagnostics are plain missing
sources, so they report the shared stats and diagnostics reasons instead
of per-code strings. The version rule keeps its own reason, now only for
an unrecognized backend or version, and the ICU rule keeps its reason for
a plugin list skipped because the version could not be read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [MM-71058] Share the indexing gate across the search rules

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71058] Tighten the search rule edge cases

Report an uncollected cluster version as unavailable, mask passwords
containing '@', treat batch sizes <= 1 as synchronous, and flag
unspecified addresses as local.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71058] Fire the search scale rules above the post thresholds

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Add SEARCH_UNAVAILABLE rule and no-fallback ES_SERVER_ERROR message

SEARCH_UNAVAILABLE fires when SqlSettings.DisableDatabaseSearch is on and
Elasticsearch searching is off, so searches silently return nothing.
ES_SERVER_ERROR now says search is fully down when there is no database
fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71058] Lower ES_MISSING_ICU to info and drop the batch size default

The plugin list is advisory and cannot prove a plugin is absent, so the
missing ICU finding is informational until per-node plugin data exists.

* [MM-71058] Avoid duplicate search findings and simplify the search rules

The scale rules resolve when database search is disabled, and
ES_SERVER_ERROR uses the no-fallback message only while Elasticsearch
serves search, so SEARCH_UNAVAILABLE alone reports a fully disabled search.
The loopback check also matches localhost. and *.localhost.

* [MM-71058] Split the unsupported search version message by direction

Upgrading the search cluster is the wrong advice when it is newer than
the supported range, so the finding message now says whether the cluster
is too old or too new, and the remediation is direction-neutral.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 08:47:23 +02:00
03f584b735 test(e2e): add pw.ensureDemoPlugin fixture, dedupe demo plugin URL (#38918)
* test(e2e): add pw.ensureDemoPlugin fixture, dedupe demo plugin URL

Centralizes the demo plugin id/URL/version into a single constant
(lib/src/constant.ts) instead of three separate hardcoded copies, and
replaces the old setupDemoPlugin(adminClient, pw) helper — called
redundantly at the top of nearly every demo plugin spec — with a
pw.ensureDemoPlugin({activate}) fixture.

The setup project now installs and configures the plugin once for the
whole run via pw.ensureDemoPlugin({activate: false}); individual specs
activate it themselves via the (default activate: true) fixture call,
which is now a cheap no-op once the plugin is already installed and
active — patching PluginSettings/reinstalling on every call was found
to make the plugin supervisor reload repeatedly and eventually get
stuck inactive after enough consecutive specs.

Also bumps the demo plugin to v0.12.1 (fixing a previously-skipped
test), removes now-unnecessary test.setTimeout calls and magic-number
assertion timeouts now that activation is fast, and reformats demo
plugin spec comments to the repo's "# step / * assertion" convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(e2e): force a clean plugin reload after crash recovery

ensureDemoPlugin() is a no-op once isPluginActive() is true, so it no
longer re-applies patchConfig on every demo-plugin spec the way the
old setupDemoPlugin() did. That unconditional per-spec patchConfig
call was incidentally triggering a full plugin supervisor reload (a
clean OnActivate) before nearly every spec, which papered over any
partial corruption left by plugin_crash.spec.ts's deliberate crash:
the server's auto-restart only guarantees isPluginActive flips back
to true, not that every hook (interactive dialog submit, post action
callbacks) came back in a working state.

With that incidental self-healing gone, whichever demo-plugin spec
happens to run next after plugin_crash.spec.ts in a given worker can
inherit a plugin that reports active but whose submit/callback paths
are still broken - matching the CI failures on PR #38918 in
demo_ephemeral.spec.ts, demo_dialog.spec.ts, demo_dialog_date.spec.ts,
and demo_dialog_field_refresh.spec.ts (dialogs open fine but never
close after submit, or post actions never update the post), always
exactly one such failure per run, immediately downstream of the crash
test.

Force a real disable/enable cycle after verifying crash recovery so
later specs in the same worker inherit a fully, not just nominally,
recovered plugin.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): fix prettier formatting in demo plugin fixture files

npm run check (ci/playwright/npm-check) was failing on this branch:
lib/src/index.ts, lib/src/server/plugin.ts, and plugin_crash.spec.ts
had pre-existing prettier violations (lines exceeding printWidth).
Re-formatted with 'prettier --write' so lint/prettier/tsc all pass
locally before push.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): fix test-isolation bug in demo_sidebar_components.spec.ts

The first test asserted the hooks toggle indicator shows 'Enabled' as an
ambient precondition without first establishing that state. A concurrently
running instance of this spec (e.g. via --repeat-each) or another spec
toggling the same global /demo_plugin hooks state could leave it disabled,
causing a flaky failure on the initial assertion. Explicitly enable hooks
first, using the same resilient retry pattern as demo_plugin_hook_toggle.spec.ts
(re-enable via adminClient + poll for active state if the plugin is
transiently unavailable).

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): add crash-tolerance retries to demo plugin webapp specs

demo_channel_header_button.spec.ts and demo_root_modal_post_dropdown.spec.ts
interact with demo-plugin-rendered UI (App Bar button, post actions menu
items) with no tolerance for the plugin being transiently unavailable, e.g.
while a concurrently running plugin_crash.spec.ts worker is mid
crash-recovery. Add retry loops (reload + re-click for the App Bar button;
reopen menu + re-click for the post actions menu items) matching the
resilience pattern already used elsewhere in the demo_plugin suite
(demo_dialog.spec.ts, demo_interactive.spec.ts, demo_show_mentions.spec.ts).

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): retry dialog submit/cancel actions on transient plugin failure

CI reported consistent failures (reproduced identically across the
enterprise job and 3 separate upgrade-matrix jobs) where demo_dialog.spec.ts
and demo_dialog_field_refresh.spec.ts's dialog remained open after clicking
Submit/Create Project, or demo_dialog.spec.ts's cancellation post never
appeared after clicking Cancel. In all cases the dialog opened successfully
(so the earlier open-dialog retry wasn't the issue); the submit/cancel
network request itself silently failed, consistent with the demo plugin
being transiently crashed/unavailable due to a concurrently running
plugin_crash.spec.ts worker.

Add retry loops around the submit/cancel actions: re-click Submit/Create
Project (idempotent — filled-in fields are retained) if the dialog doesn't
close, and reopen+re-cancel the dialog if the cancellation post doesn't
appear after a successful close. Also harden demo_dialog_field_refresh.spec.ts's
initial dialog-open retry and demo_ephemeral.spec.ts's /ephemeral_override
command with the same sendDemoSlashCommand + retry pattern used elsewhere.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): widen demo plugin retry budgets to outlast plugin_crash recovery

CI still showed demo_dialog.spec.ts, demo_dialog_field_refresh.spec.ts, and
demo_ephemeral.spec.ts failing after the previous retry fix, plus newly
surfaced the same failure mode in demo_interactive.spec.ts and
demo_dialog_date.spec.ts. plugin_crash.spec.ts's documented worst-case
recovery (crash detection + isPluginActive poll + forced disable/enable
cycle) is ~50s, but the prior retry loops only budgeted 4 x 10s = 40s,
and the suite's default 60s test timeout left no room to extend that
within the existing structure.

Add test.slow() (triples the per-test timeout) to each affected test and
widen the submit/click retry loops to 8 attempts (~80s), comfortably
outlasting the documented recovery window. Also add the same retry
pattern to demo_ephemeral.spec.ts's Update/Delete button clicks and
demo_interactive.spec.ts's Interactive Button click, which had no retry
at all despite making the same kind of request that silently fails
during a plugin crash.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): widen dialog submit retry budget to outlast compounded plugin_crash recovery

CI on this PR observed demo_dialog_date.spec.ts failing twice across the
rolling-upgrade matrix (release-11.9, release-11.10) with the dialog still
visible after exhausting the 8-attempt/~80s submit-retry budget. Root
cause: if a concurrent plugin_crash.spec.ts worker itself needs a
Playwright CI retry, its crash+recovery cycle effectively runs twice
back-to-back, pushing the broken-hook window past our originally assumed
~50s (and past the 80s retry budget meant to cover it) to ~100s+.

Widen the submit/cancel retry loops in demo_dialog.spec.ts,
demo_dialog_date.spec.ts, and demo_dialog_field_refresh.spec.ts from 8 to
12 attempts (~120s), and switch from test.slow() (an implicit 3x
multiplier) to an explicit test.setTimeout(duration.four_min) so the
outer test timeout has clear, generous headroom over the retry budget.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): restore ensureDemoPlugin/demoPluginId exports dropped while adding permalinkUrl

Adopting permalinkUrl() from another branch's index.ts/server/index.ts via
a wholesale file checkout clobbered this PR's own ensureDemoPlugin,
demoPluginId, demoPluginUrl, and EnsureDemoPluginOptions exports (added
earlier in this branch, after the other branch's copy was taken). This
broke the lib's rollup build (test_fixture.ts imports ensureDemoPlugin
from ./server) for any uncommitted work importing permalinkUrl, though it
never reached a commit or CI.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): fix CPA field reuse to reset leftover attrs and permission pins

setupCustomProfileAttributeFields() reused a same-name/type field as-is,
silently inheriting whatever visibility/managed/value_type/display_name a
prior test left on it. Because custom profile attribute field names are
unique server-wide and ABAC's edit_policies tests share this same helper
and field namespace (both live in the AccessControlPropertyGroupName
property group), a field created by one suite (e.g. ABAC's
'Department'/'Office' with managed: 'admin') could be silently reused by
an unrelated custom_attributes.spec.ts test expecting a plain field, and
vice versa. This isn't a concurrency issue - these specs run sequentially,
one spec file at a time, within a single shard - it's state one test
leaves behind that the next test (same file retried, or a different file
later in the same shard) doesn't expect.

On a type or managed-state match, now patch the reused field's
visibility/value_type/display_name back to this call's request (nulling
any key the caller didn't specify so the merge-patch deletes the stale
value instead of preserving it). On a type or managed-state mismatch -
including a field whose permission_values is already stuck at sysadmin
from a prior managed: 'admin' state, since the server's merge-patch never
downgrades that pin - delete and recreate instead, so permissions are
computed fresh for the new state.

Also replaces edit_policies.spec.ts's MM-T5791/MM-T5792 destructive
delete-all-then-recreate setup (which silently swallowed delete errors,
letting an undeletable field collide with the very next unconditional
create) with this same reuse-by-name helper, matching the pattern
MM-T5790 already uses.

Verified by running edit_policies.spec.ts and custom_attributes.spec.ts
back to back against the same server (reproducing same-shard execution
order) three times via testcontainers: all 15 tests pass on every run.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): recover demo plugin interactive hooks mid-retry instead of blaming plugin_crash.spec.ts

demo_dialog.spec.ts (submit and cancel), demo_interactive.spec.ts, and
demo_dialog_field_refresh.spec.ts each retry their interactive-hook action
(dialog submit/cancel, button click) on transient failure, with comments
attributing the failure to a concurrent plugin_crash.spec.ts worker
recovering the shared demo plugin. That theory doesn't hold up: the
matrix's playwright jobs are one Playwright worker each with their own
isolated server, so there's no concurrent worker to blame, and CI job
logs confirm plugin_crash.spec.ts never ran on the shards that hit this
failure. The hooks genuinely stop responding for the rest of a worker's
run, for reasons not yet understood, and plain action retries don't clear
it.

Add recoverDemoPlugin() (disable/enable/poll-active, the same cycle
plugin_crash.spec.ts already uses after a deliberate crash) and call it
partway through each retry loop as a last-resort recovery step once plain
retries have had a chance to clear a merely-transient failure first.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): force profile-popover load before notification race check; widen masking cache-warmup retry budget

desktop_notification_support.ts: the marker-post-rendered signal doesn't guarantee the async
sender-profile fetch (which getNotificationUsername's getUser(state, post.user_id) depends on)
has resolved, so a notification firing right after can still race it and fall back to the raw
websocket sender_name. Open and close the marker post's profile popover, which only becomes
visible once its data has loaded, to force and wait out the same fetch.

masking_admin_roles.spec.ts: the ABAC enforcement cache can take longer than 6 retries to warm
up on a cold cache; widen the retry budget to 10 attempts and align the final assertion's
timeout with the loop's own per-attempt wait, with a proportionally increased test timeout.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): fix apostrophe_values.spec.ts policy-list pagination miss and ownedIds cleanup typo

The policy list page paginates at 10 rows; by the time this test runs, other
ABAC specs earlier in the same shard can have accumulated dozens of
policies, pushing the newly created one off page 1 so the row lookup never
finds it. Filter with the list's own Search box instead of scanning the
unfiltered first page, and widen the row-visibility timeout to leave room
for the list refresh after a save.

Also fix the afterEach cleanup call, which built the ownedIds marker under
the key __ownedIds instead of the ownedIds property deleteCustomProfileAttributes
actually checks for; the mismatch made it silently fall back to treating every
key of the plain object -- including the literal string "__ownedIds" -- as a
field ID to delete, producing a harmless but noisy 404 on every run.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): widen DirectChannelsModal.selectUser's remove-button timeout

Selecting a row re-renders the modal's selected-user chips; under load in CI
this can take longer than the default 10s expect timeout, causing MM-T460 and
MM-T466/MM-T480-style flakes in specs that select multiple group-message
members in sequence.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): avoid re-querying search after the indexing poll succeeds

postAndFindMessage polled userClient.searchPosts until it returned results,
then made a second, independent search call for the assertions. That second
call can race the search backend's own eventual consistency (e.g.
Elasticsearch's near-real-time refresh) and transiently see zero results
even though the immediately preceding poll attempt just found the post.
Reuse the exact results snapshot that satisfied the poll instead.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): surface CPA visibility-patch failures instead of swallowing them

updateCustomProfileAttributeVisibility() caught and console.log'd any patch
failure, then returned as if it had succeeded. If the patch is ever rejected
(e.g. a field left in a state the server won't allow a visibility patch
against) the test would silently keep going with the old visibility and fail
confusingly on a UI assertion minutes later instead of on the real cause.
Let the error propagate, and also verify the server echoed back the visibility
we asked for rather than trusting a 200 alone.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): add missing plugin-recovery cycle to demo_dialog_date.spec.ts

This spec's submit retry loop never forced a recoverDemoPlugin() cycle the
way its sibling demo_dialog.spec.ts does, so if the interactive dialog submit
hook ever got stuck it had no way to recover within the retry budget — it
would just keep clicking the same broken Submit button 12 times. Add the same
attempt-6 recovery cycle demo_dialog.spec.ts uses. Also corrects the spec's
doc comments from 'concurrent worker' framing to match how CI actually runs
these specs: one worker per job, tests run sequentially against a single
shared server, so the risk is prior-test state leakage within the same
worker, not true concurrency.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): wait for attribute view sync before checking masking RHS tags

MM-68508-23's CEL expression ANDs three separate user attributes together,
but unlike the other masking_admin_roles tests (which wait for
waitForAttributeViewToInclude before relying on policy evaluation), this one
went straight to polling the channel members RHS for the enforcement cache to
warm up. Under load a three-attribute expression can take longer for the
user-attribute view to resolve than the UI retry loop budgets for, so wait
for the attribute view to reflect the full expression first, the same way the
sibling tests in this file already do before assigning policies.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): never reuse a CPA field linked to a Global Attribute template

setupCustomProfileAttributeFields matched existing fields purely by name,
type, and managed state. A field left behind by a Global Attributes test
(linked_field_id set, e.g. a leftover 'Department' linked to a template's
'Applies to: User' resource) matched on all three and was reused as-is,
then later patched by updateCustomProfileAttributeVisibility -- which the
server now surfaces as a real error since that helper no longer swallows
failures: 'This field is linked to a Global Attribute template and cannot
be modified through the User Attributes API.' This was causing MM-T5776
to fail deterministically across the upgrade matrix jobs.

Detect linked_field_id on the existing field and route it through the
same delete+recreate path used for a type/managed mismatch, using the
generic property-field delete route (the CPA-specific one also rejects
deleting a linked field, since it exists to protect a live template's
dependents -- not to protect the dependent field itself).

Verified via testcontainers: reproduced the exact CI error message by
seeding a template + linked 'Department' field, confirmed
setupCustomProfileAttributeFields now returns a fresh unlinked field, and
confirmed updateCustomProfileAttributeVisibility succeeds against it.
custom_attributes.spec.ts passes x3 after the fix.

Co-authored-by: saturnino <saturnino@mattermost.com>

* debug(e2e): instrument demo plugin dialog retries to diagnose CI-only hang

Temporary, diagnostic-only instrumentation to narrow down why the demo
plugin's dialog submit/cancel hooks go permanently unresponsive partway
through some full-matrix CI runs, a symptom that recoverDemoPlugin()'s
disable/enable cycle does not fix (see its doc comment).

Logs, via console.log so it lands directly in CI job output:
- ServiceSettings.SiteURL, AllowedUntrustedInternalConnections, and
  OutgoingIntegrationRequestsTimeout at the start of each retry loop and
  around recoverDemoPlugin()'s disable/enable cycle.
- Demo plugin active/installed state at the same points.
- The actual HTTP status of every /api/v4/actions/dialogs/submit response
  the browser receives, so we can tell whether the request ever completes,
  hangs, or errors server-side.

Local reproduction against the exact real-CI predecessor specs (and an
in-progress larger-scale repro against the full auth+channels suite) has
not reproduced the hang, so this is meant to capture evidence from a real
CI run instead, where the bug has consistently reproduced. Intended to be
reverted once the root cause is confirmed.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): extend MM-68508-23 timeout budget for the attribute-view wait

The waitForAttributeViewToInclude() call added for this test's three-attribute
CEL expression can take up to 45s on its own, on top of the existing 10-attempt
UI retry loop's already-documented ~180s worst case. test.setTimeout(180000)
was sized only for the retry loop and left no room for the added wait, risking
a silent global test-timeout cutoff mid-retry (which surfaces as a generic
timeout error instead of the specific assertion failure, making a real
regression harder to diagnose). Bump to 240000 to cover the combined worst
case.

Co-authored-by: saturnino <saturnino@mattermost.com>

* TEMPORARY(validation-only): cherry-pick plugin activation race fix for real-CI confirmation

DO NOT MERGE. This is a throwaway validation commit, not a permanent part of
this e2e-test branch.

Cherry-picks the server-side fix from PR #38989
(cursor/fix-plugin-activation-race-condition-be2b, commit 1b82248315) onto
this branch so that CI builds a server docker image that includes the fix
and runs the full Playwright suite (including the previously-flaky
demo_plugin specs and the rolling-upgrade matrix) against it. This gives
real-CI-level confirmation of the fix ahead of/independent from it landing
in master's own built image.

This commit MUST be reverted once the validation CI run completes (pass or
fail) and the result has been reported, so this branch goes back to
exercising the real master-tracked server image + the real fix's merge
path via PR #38989, not a temporary cherry-pick.

See PR #38989 for the actual fix and its rationale/verification.

Co-authored-by: saturnino <saturnino@mattermost.com>

* Revert "TEMPORARY(validation-only): cherry-pick plugin activation race fix for real-CI confirmation"

This reverts commit 9b34ca43a7.

* style(e2e): fix prettier line-wrap in demo plugin diagnostics

Co-authored-by: saturnino <saturnino@mattermost.com>

* Revert non-demo-plugin E2E changes

Scope this branch to demo plugin Playwright E2E tests only. Reverts
unrelated CPA, ABAC, search, DirectChannelsModal, and permalink
helper changes back to their pre-branch state.

Co-authored-by: saturnino <saturnino@mattermost.com>

* Remove temporary debug instrumentation from demo_dialog spec

Interactive-hook flakiness is understood and handled by
recoverDemoPlugin(); the investigation-only logging/response tracer
is no longer needed. Verified: full demo_plugin suite passes in
natural, crash-first, and crash-last orderings (0 failures).

Co-authored-by: saturnino <saturnino@mattermost.com>

* Fix package-lock.json after merge

Git's line-based merge of package-lock.json dropped @emnapi/core and
@emnapi/runtime entries that master added, breaking npm ci. package.json
is now identical to master's, so take master's lockfile verbatim.

Co-authored-by: saturnino <saturnino@mattermost.com>

* test(e2e): align demo plugin retries and channelname with review feedback

Use demo_plugin as the installation spec channel name, move the App Bar
click into the header-button retry, and check for a late result before
repeating Update, Delete, Submit, or interactive clicks.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Fix upgrade-to project detection in worker processes

Workers don't inherit --project in argv, so isUpgradeToPhaseProjectSelected()
always returned false there, causing the swap-to test to boot the stale
persisted image instead of the real to-image. Propagate from/to-phase
selection via env var like the existing generic flag. Also scope the
one-time fresh-start/prior-run checks in playwright.config.ts to the
runner process, since they now correctly re-fire in workers too.

Co-authored-by: saturnino <saturnino@mattermost.com>

* Fix remaining coderabbit review issues in demo plugin specs

- demo_dialog.spec.ts: treat a failed dialog reopen during the
  cancellation retry loop as a failed attempt instead of aborting
- demo_ephemeral.spec.ts: use explicit four-minute timeout to match
  the other retry-loop specs instead of test.slow()

Co-authored-by: saturnino <saturnino@mattermost.com>

* Improve dialog handling and timeout settings in demo plugin tests

* drop demo plugin retry loops and make install spec really install

- Remove attempt/recovery loops and recoverDemoPlugin from the demo plugin specs.
- Re-activate the plugin in hook_toggle so it creates a channel for the new team, and re-enable that test.
- Remove the plugin first in demo_plugin_installation so it actually installs from the URL.

* test: re-add cancellation verification for Sample Confirmation Dialog

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: saturnino <saturnino@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-19 06:35:00 +00:00
Bill Gardner 3051bab1b2 update Calls to v1.12.6 (#39219)
Automatic Merge
2026-10-19 08:31:48 +03:00
f38678d08d [MM-71099] Health Dashboard PR22: rules for cluster, deployment and network (#39022)
* [MM-71099] Read node versions from cluster info first

A live snapshot attaches diagnostics to the leader only, so NodeVersion read
the leader's version from diagnostics (CurrentVersion) and every follower's
from gossip (CurrentVersion plus the build number). A healthy cluster then
looked like two versions. Reading cluster info first, as SchemaVersion does,
answers every live node from gossip; packet and standalone nodes have no
cluster info and still answer from diagnostics.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71100] Health Dashboard: name the standalone leader node from its diagnostics

A live standalone snapshot left the leader's hostname empty, while the packet
reader names the node from diagnostics.yaml. Node-scoped findings then got a
different scope, and so a different fingerprint, live and offline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71099] Health Dashboard PR22: rules for cluster, deployment and network

Adds eight checks to rules/cluster: gossip encryption, the LRU cache at
scale, a configured cluster running a single node, an unreachable advertise
address and a writable file config under HA, rate limiting off on a large
instance and an undersized rate limit store, and version skew between
nodes. Version skew emits one global result plus one result per node.

Nothing reads NodeSnapshot.ConfigHash now that config drift is deferred, so
it is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71099] Health Dashboard: give the test packets the settings the cluster rules read

The fixture configs and the mmctl test packet were trimmed to the settings
earlier rules read, so the new rules reported them as unknown. They now carry
the server defaults for gossip encryption, the cache and rate limiting, and
every new rule resolves on them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [MM-71099] Health Dashboard: make CLUSTER_GOSSIP_UNENCRYPTED internal

Keep the gossip encryption finding off the product surface for now.
TestRegistered asserts each rule's surface instead of product for all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: drop the build number from gossip node versions

Gossip advertises CurrentVersion.BuildNumber while support packets carry
only CurrentVersion, so a build-number-only difference fired version skew
live but not offline, and live details showed build numbers. NodeVersion
now keeps the first three parts of the gossip version, so live and
offline always agree. The System Console cluster table still warns on a
build-only difference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: move the rate-limit rules to the platform area

The rate limiter is a per-node API setting with nothing to do with HA, and
RATELIMIT_OFF_LARGE fires on single-node installs too. RATELIMIT_OFF_LARGE
and RATELIMIT_STORE_SMALL move to rules/platform with AreaPlatform, next to
the other server runtime settings.

platform_test.go shares its name with the platform rules PR's helpers on
purpose, so merging the two conflicts instead of silently redeclaring them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: reword the version skew no-majority reason

Say in plain words that the nodes are split between versions, and fix the
translator description: a tie for the most common version, not only an
even split, leaves a node unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: surface CLUSTER_GOSSIP_UNENCRYPTED as info

Show the gossip encryption finding to admins at info severity: it only
fires when the default was explicitly turned off. The remediation now
says the nodes must restart together, since encrypted and unencrypted
nodes cannot talk to each other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: make RATELIMIT_OFF_LARGE internal info

Rate limiting is off by default and the docs point larger installs at
throttling in a load balancer, which the rule cannot see, so it would
warn on most correctly run large installs. Keep it for CSE only.
TestRegistered asserts each rule's surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: compare the rate limit store with the user count

The docs size RateLimitSettings.MemoryStoreSize to the expected number
of users, so a small install with a store below the default but above
its user count is configured correctly. RATELIMIT_STORE_SMALL now fires
when the store is smaller than the registered users, and is unknown
without stats. The mmctl test packet gets stats so it still resolves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [MM-71099] Health Dashboard: count only connected nodes for CLUSTER_SINGLE_NODE

NumberOfNodes comes from GetClusterInfos, which also returns nodes that
are registered in the database but not in the gossip cluster. A node cut
off from gossip keeps pinging the database, so the rule resolved in the
exact case it is meant to catch. The support packet now writes
number_of_connected_nodes, counting only gossip members (those with an
IP address), and the rule reads it. Packets without the field are
unknown rather than falling back to the inflated count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-10-18 22:11:02 +02:00
f5227e664f [MM-71096] Apply incoming DeleteAt when receiving synced reactions (#39239)
* [MM-71096] Apply incoming DeleteAt when receiving synced reactions

The shared channel receiver toggled reactions based on whether a matching
row existed, ignoring the incoming DeleteAt. Re-sent active reactions were
deleted, removed reactions the receiver never had were created as active,
and remote-initiated removals rewrote the row's RemoteId to empty so they
echoed back to the origin.

Apply the sender's state idempotently instead, and keep the remote's ID on
the row when deleting so the removal is not synced back.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Retrigger CI after hung Postgres shard and unrelated Enterprise FIPS test failure.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 0 answered, 2 resolved, 0 declined

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-10-17 23:00:10 -04:00