* Add unread badge to Recaps sidebar link Shows the count of unread finished recaps (completed or failed) on the LHS Recaps link. Pending and processing recaps are excluded so the badge only reflects work the user can actually read. When any unread recap has failed, the badge is colored as an error to surface the failure. The badge updates live through the existing recap_updated WebSocket event, which refreshes the recap in the Redux store. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix Recaps failed-badge color losing to active sidebar rule The failed-badge modifier selector had the same specificity (0,4,0) as `.channel-view .sidebar--left .active .badge` in _badge.scss, so when the Recaps link was the active route the global mention background color won on cascade order. Scope the rule with `#SidebarContainer` so it wins on specificity (1 id + 4 classes) regardless of active state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix Recaps badge selector memoization getUnreadFinishedRecapsBadge was keyed off getAllRecaps, which is not memoized and returns a new array on every call. That broke reselect's reference-equality input check, so the selector recomputed and returned a fresh {count, hasFailed} object on every store dispatch — forcing RecapsLink (always mounted when the feature flag is on) to re-render on every action. Key the selector off state.entities.recaps directly and iterate ids in the result function so memoization holds when the recaps slice is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address PR feedback on Recaps sidebar badge - Pass shallowEqual to the useSelector consuming getUnreadFinishedRecapsBadge. The selector returns a plain {count, hasFailed} object, so recap updates that change a recap but leave the badge values the same (e.g. marking a read recap) would otherwise force RecapsLink to re-render. - Scope the "no badge" negative assertion to the render container so it only asserts on the badge element, not any '1' or '.badge' elsewhere in the DOM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address UX feedback on Recaps sidebar badge - Add `unread` class to the sidebar item and `unread-title` to the link when there are unread recaps so the label goes bold and the icon goes full-opacity, matching how channels and the threads link indicate unread state. - Keep the badge (and the new failed icon) visible on hover so it doesn't disappear under the cursor -- same override the threads link uses. - Replace the red failed-badge modifier with an amber alert icon rendered in place of the count badge when any unread recap has failed. Red mention badges are reserved for urgent priority messages and caused confusion here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Keep Recaps badge in place on hover The global sidebar hover rule shrinks padding-right from 16px to 5px to make room for the per-channel menu button, which shifted the badge right since it stays visible. Restore padding-right: 16px on hover for the Recaps link, matching what the threads link already does. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align Recaps failed-icon aria-label with tooltip The aria-label on the .RecapsFailedIcon span was a hardcoded English string ("Recap failed") that differed from the tooltip shown to sighted users ("One or more recaps failed"). Derive the aria-label from the same intl message used by the tooltip so screen readers and sighted users get the same wording and the label is localized. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Stop Recaps link from overriding global unread label styling The combined `.active .SidebarLink, .SidebarLink.unread-title` rule pushed font-weight: 400 onto .SidebarChannelLinkLabel with specificity (0,4,0), overriding the global `.SidebarChannel.unread` rule that sets font-weight: 600 and --sidebar-unread-text at (0,3,0). As a result the Recaps label rendered at normal weight when unread, inconsistent with channels and the threads link. Split the rules: keep the active-state overrides as they were, and limit the unread-title rule to the icon-specific styling Recaps actually needs, letting the global unread styling apply to the label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add i18n entry for Recaps failed-tooltip Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * change size of alert icon * fix the right icon * Add ViewedAt to recaps and POST /recaps/mark_viewed endpoint Introduce a new ViewedAt field on Recap, separate from ReadAt, that tracks whether the user has at least seen a finished recap on the recaps page. ReadAt keeps its existing per-recap "Mark read" semantics. - New Postgres migration 000172 adds the ViewedAt column (default 0) and an idx_recaps_user_id_viewed_at index mirroring the existing ReadAt index. - New store method MarkRecapsAsViewed(userId, statuses) does a single UPDATE ... WHERE ViewedAt = 0 AND Status IN (...) RETURNING Id so the app layer can fan out one WS event per affected recap. - New App.MarkRecapsAsViewed(rctx) marks the user's not-yet-viewed completed/failed recaps and broadcasts WebsocketEventRecapUpdated per affected id. - New POST /recaps/mark_viewed handler. Registered before the {recap_id} regex routes so mark_viewed isn't captured as an id. - RegenerateRecap now resets ViewedAt = 0 so a regenerated recap is surfaced again in the badge once it completes. As a related fix, UpdateRecap now persists ReadAt and ViewedAt -- previously it silently dropped the ReadAt = 0 reset that RegenerateRecap was setting in memory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark recaps as viewed when the recaps page mounts Wire the new server endpoint into the webapp: - Recap type now includes viewed_at: number. - Client4.markRecapsAsViewed posts to /recaps/mark_viewed. - New markRecapsAsViewed redux action, fired alongside getRecaps and getAgents in the recaps page mount effect. The server broadcasts recap_updated per affected recap so other tabs/devices receive the update through the existing handleRecapUpdated WS handler -- no new client-side handler needed. - getUnreadFinishedRecapsBadge now filters on viewed_at === 0 instead of read_at === 0, so the sidebar badge clears on page open instead of requiring per-recap "Mark read" clicks. Selector tests updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review feedback on Recaps viewed_at change - Defer markRecapsAsViewed until after getRecaps resolves on the recaps page mount. Previously they ran in parallel, so getRecaps could land last and overwrite the viewed_at: <now> timestamps the WS-driven refresh had just written, briefly re-showing the badge. - Switch the markRecapsAsViewed audit log to LevelContent and record the affected ids as result state, matching the pattern of every other mutating recap handler (markRecapAsRead, deleteRecap, etc). recap_count meta is now recorded unconditionally. - Add an app-layer test that asserts MarkRecapsAsViewed publishes a recap_updated websocket event for each affected recap. The fan-out is the entire reason this lives in the app layer, so a regression removing the publish loop should fail loudly. - Add a store-layer regression test that UpdateRecap actually persists ReadAt = 0 / ViewedAt = 0 resets, guarding the regenerate flow against a future change that drops those columns from the update map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Update migrations.list for 000172_add_recaps_viewed_at Regenerated via `make migrations-extract` so the autogenerated sequence list includes the new recaps ViewedAt migration files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use AddMeta for Recaps mark_viewed audit ids AddEventResultState takes a model.Auditable, not a plain map[string]any, so the previous attempt to record the affected ids did not compile. Record them as audit metadata instead, matching the pattern used by getRecaps which similarly returns a slice and uses AddMeta only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Split Recaps ViewedAt index into a CONCURRENTLY migration The lint check rejects bare CREATE/DROP INDEX in migrations because they take an ACCESS EXCLUSIVE lock and block DML. Split the index off into 000173 with CONCURRENTLY + the morph:nontransactional directive, matching the pattern used by 000168/000169 (LinkedFieldID column + its index). 000172 keeps just the ALTER TABLE ADD COLUMN, which can stay transactional. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add viewed_at to existing Recap test fixtures The Recap type now requires viewed_at, so the fixtures in recap_item.test.tsx, recap_processing.test.tsx, and recaps_list.test.tsx need it too. CI was rejecting them with TS2741. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mock markRecapsAsViewed in recaps.test.tsx The mount effect now also dispatches markRecapsAsViewed, but the manual jest.mock for 'mattermost-redux/actions/recaps' only exposed getRecaps, so the runtime call resolved to undefined and crashed with "markRecapsAsViewed is not a function". Add the missing entry to the mock. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add /recaps/mark_viewed and Recap.viewed_at to OpenAPI spec The recap-spec validator rejected the new POST /api/v4/recaps/mark_viewed handler because it had no documented operation. Add the path with its MarkRecapsAsViewed operationId, response shape, and behavior, and add the new viewed_at timestamp field to the Recap schema in definitions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fill in app.recap.mark_viewed.app_error translation The new MarkRecapsAsViewed app method references this i18n key but the en.json entry was added with an empty translation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Skip markRecapsAsViewed when getRecaps fails Marking recaps as viewed implies the user just looked at them. If getRecaps fails the user is staring at an error/empty state, so we shouldn't ack them on the server. Gate the dispatch on the thunk's result.error -- the codebase's bindClientFunc swallows errors and returns {error}, so the conventional try/catch pattern doesn't apply here. Update the recaps.test.tsx dispatch mock to return a resolved promise so the new awaited result has the expected shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Clear and assert markRecapsAsViewed mock in recaps.test.tsx Reset the new mock in beforeEach so it doesn't carry state across tests, and assert that the mount effect dispatches markRecapsAsViewed after getRecaps resolves. Awaiting via waitFor since the mark fires inside an async fetchData chain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mattermost API Documentation
This repository holds the API reference documentation for Mattermost available at https://developers.mattermost.com/api-reference.
The Mattermost API reference uses the OpenAPI standard and the ReDoc document generator.
All documentation is available under the terms of a Creative Commons License.
Contributing
We're accepting pull requests! See something that could be documented better or is missing documentation? Make a PR and we'll gladly accept it.
All the documentation is written in YAML and found in the v4/source directories. APIv4 documentation is in the v4 directory. APIs for Playbooks are retrieved from GitHub at build time and integrated into the final YAML file.
- When adding a new route, please add it to the correct file. For example, a channel route will go in channels.yaml.
- To add a new tag, please do so in introduction.yaml
- Definitions should be added to definitions.yaml
There is no strict style guide but please try to follow the example of the existing documentation.
To build the full YAML, run make build and it will be output to v4/html/static/mattermost-openapi-v4.yaml. This will also check syntax using swagger-cli.
To test locally, run make build, make run and navigate to http://127.0.0.1:8080. For any updates to the source files, re-run the same commands.
Deployment
Deployment is handled automatically by our Github Actions. When a pull request is merged, it will automatically be deployed to https://developers.mattermost.com/api-reference.