Files
mattermost/docs/site
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
..

Mattermost Documentation Site

Docusaurus workspace for docs.mattermost.com, living in the mattermost/mattermost monorepo at docs/site/.

Content layout

Directory Route Description
docs/main/ / User and admin documentation
docs/develop/ /developers Developer documentation
docs/api/ /api API reference intro + generated OpenAPI pages

Paths are relative to the repository root. The Docusaurus site reads them via the relative paths ../main, ../develop, ../api (from docs/site/).

Prerequisites

  • Node.js ≥ 20 — use nvm use inside docs/site/ to pick up .nvmrc
  • Go and make (required for the OpenAPI prebuild step, and for two of the three plugin SDK reference generators — see below)
  • Vale ≥ 3 (for content linting)

Local development

cd docs/site
npm ci
npm start          # dev server at http://localhost:3000

Sidebar generation

The documentation and developers sidebars are generated from the content directories (sidebars/documentation.generated.json / developers.generated.json, both gitignored) by npm run build:sidebars. Docusaurus imports these files directly, so they must exist before docusaurus start or docusaurus build runs — on a fresh checkout there's no other source for them. This is wired automatically via the prestart and prebuild npm lifecycle hooks, so plain npm start / npm run build just work.

(sidebars/active-redirects.json, by contrast, is committed — it's regenerated and checked in manually via node scripts/gen-active-redirects.mjs when the legacy redirect map changes, not on every build.)

Manual grouping overrides

Most top-level sections build their sidebar straight from the filesystem: each subdirectory becomes a category, each file a doc, sorted by sidebar_position frontmatter then filename. But a few sections are flat piles of 15-40 files (or split across an inconsistent filesystem nesting that doesn't reflect any real grouping) that read badly as-is, so gen-documentation-sidebar.mjs layers a manual grouping override on top of the auto-generated tree for those sections only: Overview (OVERVIEW_GROUPS/OVERVIEW_ROOT_ORDER), Deployment Guide (DEPLOYMENT_GROUPS/DEPLOYMENT_ROOT_ORDER), Administration Guide → Configure (ADMIN_CONFIGURE_GROUPS/ADMIN_CONFIGURE_ORDER), Administration Guide → Manage (ADMIN_MANAGE_GROUPS/ADMIN_MANAGE_ORDER), Administration Guide → Onboard (ADMIN_ONBOARD_GROUPS/ADMIN_ONBOARD_ORDER), Administration Guide → Scale (ADMIN_SCALE_GROUPS/ADMIN_SCALE_ORDER), End User Guide → Collaborate (COLLABORATE_GROUPS/COLLABORATE_ORDER), and Integrations Guide (INTEGRATIONS_GROUPS/INTEGRATIONS_ROOT_ORDER).

Usually the override only changes how the sidebar renders — files stay flat on disk at their existing paths, so URLs don't move. There are two exceptions, both cases where the nav home and the URL would otherwise disagree:

  • Administration Guide → Scale: 21 of its 28 files (capacity planning, HA/architecture, search infrastructure, caching) were physically moved to deployment-guide/scale/ and are grouped via DEPLOYMENT_GROUPS.scaling, matching where Sphinx's live nav lists that content. Only the 7 monitoring/observability pages stayed under ADMIN_SCALE_GROUPS.
  • The PostgreSQL and FIPS migration pages moved the other way, from deployment-guide/ to administration-guide/manage/admin/, where Sphinx's only toctree for them lives. They're grouped under ADMIN_MANAGE_GROUPS.dataMigration.

Since those moves change URLs, the Sphinx→Docusaurus redirect targets in sidebars/active-redirects.json were updated to point at the new paths, and internal .mdx links across the docs were rewritten accordingly.

Each override is a pair of constants near the top of the script:

  • A *_GROUPS map of group key → {label, landing?, items}, where items are doc basenames (relative to that section's directory) or nested inline group objects. A group's items can itself contain a nested {label, items} sub-group, which adds a third category level — a four-level path of Guide → Group → Sub-group → page — for a section large enough to need it — see OVERVIEW_GROUPS.subscription's "Cloud" sub-group, or ADMIN_MANAGE_GROUPS.userAccess's "Attribute-Based Access Control" sub-group, for existing examples.
  • A *_ROOT_ORDER/*_ORDER array listing the top-level order: plain strings for standalone docs, {group: 'key'} for a group from the map above.

There's no single shared "grouping engine" — each overridden section gets its own small buildXItem/regroupX (or buildXSidebar) pair that mirrors the shape of the others (see buildCollaborateItem/regroupCollaborate for the End User Guide → Collaborate one). Adding an override for a new section means copying that shape for the new section, not extending a shared function.

Adding a new file to one of these sections: the script fails loudly if you forget it — it logs a WARN: N file(s) missing from *_ORDER and falls back to appending the orphaned file(s) at the root of that section, so a forgotten file surfaces as a warning during npm run build:sidebars rather than silently disappearing. Add the new file's basename to the relevant group's items (or to the root order array, if it's a standalone/uncategorized page) to place it deliberately instead of leaving it at the root.

Adding a whole new manually-grouped section: copy the pattern of an existing one (Integrations Guide is the simplest example), then wire it into main() alongside the existing dir === '...' checks.

Nesting a doc from one section under a page in another section: most group items are plain basenames relative to that section's own directory, but buildAdminConfigureItem also accepts {doc: '<full id>'} for cross-directory references (their label is read directly from the target file's frontmatter via docLabelById, since it won't be in that section's leafLabels map). This is how ADMIN_CONFIGURE_GROUPS.agents nests the vendored Agents plugin pages (main/agents/docs/, staged by stage-agents-docs.mjs — not one of the TOP_LEVEL sections, so it has no top-level nav entry of its own) under administration-guide/configure/agents-admin-guide. The same page is also nested for End User Guide's end-user-guide/agents doc, but since that section has no manual grouping override at all, it uses the smaller standalone promoteDocToCategory helper instead of a full *_GROUPS override — copy that pattern for other one-off single-doc nestings rather than building a whole grouping override for a section that's otherwise fine auto-generated.

Inlining another doc's content onto a page (rather than just linking or nesting it): Docusaurus's built-in Markdown partials feature — any .md/.mdx file with a leading underscore in its name is excluded from the docs plugin's routing/sidebars and can be imported into another MDX file and rendered as <Component />. stage-agents-docs.mjs uses this to reproduce Sphinx's .. include:: /agents/docs/admin_guide.md behavior: it stages admin_guide.md/user_guide.md as normal (but unlisted: true) docs for direct-link parity, and as _admin_guide_partial.mdx/_user_guide_partial.mdx partials that administration-guide/configure/agents-admin-guide.mdx and end-user-guide/agents.mdx import and render inline — so those two pages show the full vendored guide content directly, with zero extra clicks, instead of just linking out to a separate page.

Plugin SDK reference generators

Three developer pages render content generated at build time from the plugin SDK's own source rather than hand-written prose, each backed by a gitignored JSON file under data/:

Page Generator Reads
Server plugin SDK reference scripts/gen-plugin-godocs (Go) server/public/plugin
Web app plugin SDK reference scripts/gen-plugin-jsdocs.mjs (Node) webapp/channels/src/plugins/registry.ts
Manifest reference scripts/gen-plugin-manifest-docs (Go) server/public/model's Manifest struct

They're consumed by the <PluginGoDocs />, <PluginGoExample />, <PluginJsDocs />, and <PluginManifestDocs /> components (registered globally in src/theme/MDXComponents.tsx), and run via npm run build:plugin-docs, wired into prestart/prebuild like everything else in this section. The two Go generators parse their target packages with go/parser + go/doc rather than type-checking them via golang.org/x/tools/go/packages, so they have no dependency on the Go toolchain version declared in server/public/go.mod — only stdlib, no go.sum.

The API reference section (docs/api/reference/, also gitignored) has the same requirement: docusaurus-plugin-openapi-docs needs docusaurus gen-api-docs mattermost run before it has any pages to render. prestart handles this too — using the existing OpenAPI spec if present, only falling back to the slow make -C api build spec rebuild if it's missing.

Full production build

The production build also includes an OpenAPI prebuild step (npm run build:openapi, wired to run automatically before npm run build via the same prebuild hook) that invokes make -C api build. This requires Go and takes ~2 minutes.

cd docs/site
npm ci
npm run build      # runs build:sidebars + build:openapi (via prebuild), then Docusaurus build

To skip the OpenAPI rebuild during iterative content work:

cd docs/site
npm run build -- --no-minify   # still runs build:sidebars + build:openapi first

If you need to bypass the prebuild step entirely (e.g., all generated artifacts already exist and are current), run:

cd docs/site
npm run docusaurus build       # calls docusaurus directly, skips prebuild

There's a single Algolia DocSearch app for docs.mattermost.com — credentials aren't distributed to individual developers. They're set as repository variables (vars.ALGOLIA_APP_ID, vars.ALGOLIA_SEARCH_API_KEY) and injected only in CI/CD. Local builds simply run without them: the site builds cleanly and the search bar is omitted (see the conditional in docusaurus.config.ts).

If you need to test search locally, export the same two variables in your shell before running npm start/npm run build.

Scripts

Command Description
npm start Dev server with hot reload (runs build:sidebars + build:openapi:docs first via prestart)
npm run build Production build to build/ (runs build:sidebars + build:openapi first via prebuild)
npm run build:sidebars Regenerate the documentation + developer sidebar JSON
npm run build:openapi:spec Regenerate the OpenAPI spec only (slow — invokes make -C api build)
npm run build:openapi:docs Regenerate the API reference MDX pages from the existing spec (fast)
npm run build:openapi Full OpenAPI pipeline: spec then docs
npm run build:plugin-docs Regenerate all three plugin SDK reference data files (see above)
npm run serve Serve the build/ output locally
npm run typecheck TypeScript type check
node scripts/gen-active-redirects.mjs Regenerate legacy redirect map (committed to git; run manually when it changes)

Content linting

vale main develop api