1582 Commits
Author SHA1 Message Date
25f3a75cb7 [MM-67163] Scheduled Recaps (#35495)
* docs: map existing codebase with focus on recaps

- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues

* docs: initialize project

Scheduled AI Recaps — enable recurring scheduled recaps instead of only manual one-off summaries

* chore: add project config

Mode: yolo
Depth: standard
Parallelization: enabled

* docs: complete domain research for scheduled recaps

Research files:
- STACK.md: Job server patterns, ScheduledPosts reference implementation
- FEATURES.md: Table stakes vs differentiators, anti-features to avoid
- ARCHITECTURE.md: Component boundaries, data flow, 5-phase build order
- PITFALLS.md: Timezone, cluster, schema critical pitfalls
- SUMMARY.md: Synthesized findings and roadmap implications

Key findings:
- Use ScheduledPosts polling pattern, not JobServer schedulers
- Store user intent + pre-computed NextRunAt for efficient polling
- Leverage existing recap worker, extend with time_period support
- Critical: Get timezone/schema right in Phase 1 to avoid migrations

* docs: define v1 requirements

35 requirements across 5 categories:
- Scheduling (7)
- Management (8)
- Backend Infrastructure (10)
- Frontend Wizard (7)
- Frontend Scheduled Tab (7)

Topic-based recaps deferred to v2

* docs: create roadmap (5 phases)

* docs(01): capture phase context

Phase 01: Database Foundation
- OpenCode discretion on implementation patterns
- Implicit requirements from downstream phases noted

* docs(01-database-foundation): research phase domain

Phase 1: Database Foundation
- Analyzed existing Recap and ScheduledPost patterns
- Documented schema design for recurring schedules
- Identified timezone/DST handling approach using IANA zones
- Catalogued store interface patterns
- Documented common pitfalls and anti-patterns

* docs(01): create phase plan for database foundation

Phase 01: Database Foundation
- 2 plans in 2 waves
- Wave 1: Model + migration (01-01-PLAN.md)
- Wave 2: Store layer + tests (01-02-PLAN.md)
- Ready for execution

* feat(01-01): create ScheduledRecap model with DST-aware NextRunAt computation

- Add ScheduledRecap struct with all required fields for schedule configuration
- Add day-of-week bitmask constants matching Go's time.Weekday (Sunday=0)
- Add channel mode constants (specific, all_unreads)
- Add time period constants (last_24h, last_week, since_last_read)
- Implement ComputeNextRunAt with timezone-aware scheduling using time.LoadLocation
- Implement IsValid for input validation
- Add PreSave/PreUpdate lifecycle methods
- Add Auditable method for audit logging

* feat(01-01): create database migration for ScheduledRecaps table

- Create ScheduledRecaps table with all required columns
- Add index for user queries (idx_scheduled_recaps_user_id)
- Add index for scheduler polling (idx_scheduled_recaps_next_run_at)
- Add composite index for efficient scheduler query (idx_scheduled_recaps_enabled_next_run)
- Add index for user + soft delete queries (idx_scheduled_recaps_user_delete)
- Add down migration to drop all indexes and table

* test(01-01): add unit tests for ScheduledRecap with DST edge cases

- Test day-of-week bitmask constants and operations
- Test ComputeNextRunAt for Monday-only, weekday, and every-day schedules
- Test timezone handling - different timezones produce different UTC millis
- Test DST spring forward edge case (March 2024) - Go normalizes non-existent times
- Test DST fall back edge case (November 2024) - Go uses first occurrence
- Test error cases: invalid timezone, invalid time format, zero days
- Test IsValid method for all validation rules
- Test PreSave and PreUpdate lifecycle methods
- Test Auditable method returns expected fields
- Fix ComputeNextRunAt to validate time format using regex before parsing

* docs(01-01): complete ScheduledRecap model and migration plan

Tasks completed: 3/3
- Task 1: Create ScheduledRecap model with constants and NextRunAt computation
- Task 2: Create database migration for ScheduledRecaps table
- Task 3: Add unit tests for ComputeNextRunAt with DST edge cases

SUMMARY: .planning/phases/01-database-foundation/01-01-SUMMARY.md

* feat(01-02): add ScheduledRecapStore interface to store.go

- Add ScheduledRecapStore interface with CRUD operations (Save, Get, Update, Delete)
- Add query operations (GetForUser, GetDueBefore)
- Add state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Register ScheduledRecap() method in main Store interface

* feat(01-02): create SqlScheduledRecapStore implementation

- Implement CRUD operations (Save, Get, Update, Delete with soft delete)
- Implement GetForUser with pagination for user's scheduled recaps
- Implement GetDueBefore for scheduler polling query
- Implement efficient state updates (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Handle JSON serialization/deserialization of ChannelIds array
- Follow existing patterns from recap_store.go

* feat(01-02): register ScheduledRecapStore in SqlStore

- Add scheduledRecap field to SqlStoreStores struct
- Initialize newSqlScheduledRecapStore in NewSqlStore
- Add ScheduledRecap() accessor method to SqlStore

* test(01-02): add comprehensive tests for ScheduledRecapStore

- Test CRUD operations (Save, Get, Update, Delete)
- Test GetForUser with pagination
- Test GetDueBefore scheduler query filtering
- Test state update methods (UpdateNextRunAt, MarkExecuted, SetEnabled)
- Test ChannelIds JSON serialization (array, empty, nil)
- All 13 test cases pass

* docs(01-02): complete ScheduledRecapStore plan

Tasks completed: 4/4
- Add ScheduledRecapStore interface to store.go
- Create SqlScheduledRecapStore implementation
- Register ScheduledRecapStore in SqlStore
- Create comprehensive store tests

SUMMARY: .planning/phases/01-database-foundation/01-02-SUMMARY.md

* fix(01): regenerate store mocks for ScheduledRecapStore

* docs(01): complete Database Foundation phase

Phase 1: Database Foundation
- 2 plans executed (model + store)
- 3 requirements complete (INFRA-01, INFRA-02, INFRA-10)
- Goal verified ✓

* docs(03): research phase scheduler integration domain

Phase 03: Scheduler Integration
- Standard stack identified (Mattermost job system)
- Architecture patterns documented (Scheduler + Worker pattern)
- Cluster-safe execution via leader-only scheduling
- Pitfalls catalogued (duplicate jobs, race conditions)
- Code examples from existing codebase patterns

* docs(02): create phase 2 API layer plans

Phase 02: API Layer
- 2 plans in 2 waves
- Plan 01 (Wave 1): App layer methods for CRUD + pause/resume
- Plan 02 (Wave 2): API handlers, routes, params, audit events
- Ready for execution

* feat(02-01): create App layer CRUD methods for ScheduledRecap

- Add CreateScheduledRecap with session-based userId, validation, NextRunAt computation
- Add GetScheduledRecap to retrieve by ID
- Add GetScheduledRecapsForUser with pagination
- Add UpdateScheduledRecap with NextRunAt recomputation when enabled
- Add DeleteScheduledRecap for soft delete
- Add PauseScheduledRecap to disable without deleting
- Add ResumeScheduledRecap with NextRunAt recomputation before enabling
- Regenerate store layer files for ScheduledRecapStore interface

* docs(02-01): complete App layer CRUD methods plan

Tasks completed: 3/3
- Regenerate store mocks (already complete from Phase 1)
- Create App layer file with CRUD methods
- Verify app layer interfaces (no interface file exists)

SUMMARY: .planning/phases/02-api-layer/02-01-SUMMARY.md

* feat(02-02): add audit event constants for scheduled recaps

- AuditEventCreateScheduledRecap for recap configuration creation
- AuditEventGetScheduledRecap for viewing single recap
- AuditEventGetScheduledRecaps for listing user recaps
- AuditEventUpdateScheduledRecap for configuration updates
- AuditEventDeleteScheduledRecap for recap deletion
- AuditEventPauseScheduledRecap for pausing execution
- AuditEventResumeScheduledRecap for resuming execution

* feat(02-02): add ScheduledRecapId to params and context

- Add ScheduledRecapId field to Params struct
- Parse scheduled_recap_id from URL path variables
- Add RequireScheduledRecapId validation method

* feat(02-02): add route registration for scheduled recaps

- Add ScheduledRecaps and ScheduledRecap routes to Routes struct
- Initialize route prefixes in Init function
- Add InitScheduledRecap call in initialization

* feat(02-02): create API handlers for scheduled recaps

- InitScheduledRecap registers all 7 API routes
- createScheduledRecap validates required fields and creates recap
- getScheduledRecap retrieves with authorization check
- getScheduledRecaps lists user's recaps with pagination
- updateScheduledRecap updates with ownership verification
- deleteScheduledRecap soft deletes with authorization
- pauseScheduledRecap disables execution with ownership check
- resumeScheduledRecap re-enables with NextRunAt recomputation
- All handlers include audit logging and feature flag check

* docs(02-02): complete API handlers plan

Tasks completed: 4/4
- Add audit event constants for scheduled recaps
- Add ScheduledRecapId to params and context
- Add route registration in api.go
- Create API handler file

SUMMARY: .planning/phases/02-api-layer/02-02-SUMMARY.md

* docs(02): complete API Layer phase

Phase 2: API Layer
- 2 plans executed in 2 waves
- 16/16 must-haves verified
- INFRA-05 through INFRA-09 complete
- Ready for Phase 3: Scheduler Integration

* docs(03): create phase plan for scheduler integration

Phase 03: Scheduler Integration
- 2 plans in 2 waves
- Wave 1: Job constant, scheduler, worker
- Wave 2: Job registration, App method
- Ready for execution

* fix(03): revise plan 03-02 Task 2 for worker context compatibility

- CreateRecapFromSchedule now creates recap directly via store
- Uses sr.UserId instead of rctx.Session().UserId (unavailable in worker)
- Creates JobTypeRecap job directly instead of delegating to CreateRecap
- Updated key_links to reflect store and job linkage

* docs(04): capture phase context

Phase 04: Scheduled Tab
- Implementation decisions documented
- Phase boundary established
- Figma references captured (123:62940, 123:19772)

* feat(03-01): add JobTypeScheduledRecap constant

- Add JobTypeScheduledRecap constant with value 'scheduled_recap'
- Add to AllJobTypes slice for job type validation

* feat(03-01): create ScheduledRecap scheduler

- Add Scheduler struct wrapping PeriodicScheduler
- 1-minute polling interval (SchedulerPollingInterval constant)
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- ScheduleJob polls GetDueBefore for due recaps
- Creates job with CreateJobOnce for deduplication
- Job data: scheduled_recap_id, user_id, channel_ids, agent_id

* feat(03-01): create ScheduledRecap worker

- Define AppIface interface with CreateRecapFromSchedule method
- Use SimpleWorker pattern following recap/worker.go pattern
- Enabled when cfg.FeatureFlags.EnableAIRecaps is true
- Extract job data: scheduled_recap_id, user_id, channel_ids, agent_id
- Verify ScheduledRecap exists and is enabled before execution
- Call app.CreateRecapFromSchedule to create the actual recap
- Compute next run time using sr.ComputeNextRunAt
- Call MarkExecuted to update LastRunAt, NextRunAt, RunCount atomically
- Disable non-recurring schedules after execution

* docs(03-01): complete job system components plan

Tasks completed: 3/3
- Add JobTypeScheduledRecap constant
- Create scheduler implementation
- Create worker implementation

SUMMARY: .planning/phases/03-scheduler-integration/03-01-SUMMARY.md

* docs(04): create phase 4 plans - Scheduled Tab UI

Phase 04: Frontend - Scheduled Tab
- 4 plans in 4 waves
- Wave 1: TypeScript types + Client4 API methods
- Wave 2: Redux layer (action types, actions, reducer, selectors)
- Wave 3: ScheduledRecapItem component (card UI with toggle, menu)
- Wave 4: Scheduled tab integration with human verification

Covers requirements TAB-01 through TAB-07 and MGMT-01 through MGMT-08

* feat(03-02): add ScheduledRecap job registration in initJobs

- Register JobTypeScheduledRecap with worker and scheduler
- Import scheduled_recap package for job components
- Worker uses App interface for CreateRecapFromSchedule
- Scheduler polls for due recaps at 1-minute intervals

* feat(03-02): implement CreateRecapFromSchedule App method

- Create Recap from ScheduledRecap configuration
- Use sr.UserId instead of session (worker context has no session)
- Create recap record directly via store
- Create JobTypeRecap job to trigger processing
- Handle both specific channels and all_unreads mode

* docs(03-02): complete app integration plan

Tasks completed: 3/3
- Add job registration in initJobs
- Implement CreateRecapFromSchedule App method
- Verify full integration compiles

SUMMARY: .planning/phases/03-scheduler-integration/03-02-SUMMARY.md

* docs(phase-3): complete scheduler integration phase

* feat(04-01): add ScheduledRecap TypeScript types

- Add ScheduledRecap type matching Go model fields
- Add ScheduledRecapInput type for create/update operations
- Types exported via @mattermost/types/recaps

* feat(04-01): add Client4 scheduled recap route and imports

- Add getScheduledRecapsRoute() method returning /scheduled_recaps endpoint
- Import ScheduledRecap and ScheduledRecapInput types

* feat(04-01): add Client4 scheduled recap API methods

- createScheduledRecap: POST /scheduled_recaps
- getScheduledRecaps: GET /scheduled_recaps (paginated)
- getScheduledRecap: GET /scheduled_recaps/:id
- updateScheduledRecap: PUT /scheduled_recaps/:id
- deleteScheduledRecap: DELETE /scheduled_recaps/:id
- pauseScheduledRecap: POST /scheduled_recaps/:id/pause
- resumeScheduledRecap: POST /scheduled_recaps/:id/resume

* docs(04-01): complete TypeScript types and Client4 methods plan

Tasks completed: 3/3
- Add ScheduledRecap TypeScript type
- Add Client4 scheduled recap route helper
- Add Client4 scheduled recap API methods

SUMMARY: .planning/phases/04-scheduled-tab/04-01-SUMMARY.md

* feat(04-02): add scheduled recap action types

- GET_SCHEDULED_RECAPS_REQUEST/SUCCESS/FAILURE
- RECEIVED_SCHEDULED_RECAP and RECEIVED_SCHEDULED_RECAPS
- PAUSE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- RESUME_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- DELETE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE

* feat(04-02): add scheduled recap Redux actions

- getScheduledRecaps: fetches paginated scheduled recaps
- pauseScheduledRecap: pauses a scheduled recap
- resumeScheduledRecap: resumes a paused scheduled recap
- deleteScheduledRecap: deletes a scheduled recap

* feat(04-02): add scheduled recaps to reducer

- Add scheduledRecaps to RecapsState type
- Handle RECEIVED_SCHEDULED_RECAP for single recap
- Handle RECEIVED_SCHEDULED_RECAPS for bulk updates
- Handle DELETE_SCHEDULED_RECAP_SUCCESS for removal

* feat(04-02): add scheduled recap selectors

- getScheduledRecapsState: base selector for raw state
- getAllScheduledRecaps: returns all scheduled recaps as array
- getActiveScheduledRecaps: filters enabled, non-deleted recaps
- getPausedScheduledRecaps: filters disabled, non-deleted recaps
- getScheduledRecapById: returns single recap by ID

* feat(04-02): update GlobalState type for scheduled recaps

- Import ScheduledRecap type from recaps
- Add scheduledRecaps field to recaps entity state

* docs(04-02): complete Redux store and actions plan

Tasks completed: 5/5
- Add scheduled recap action types
- Add scheduled recap Redux actions
- Add scheduled recaps to reducer
- Add scheduled recap selectors
- Update GlobalState type for scheduled recaps

SUMMARY: .planning/phases/04-scheduled-tab/04-02-SUMMARY.md

* feat(04-03): add i18n strings for scheduled recap UI

- Add scheduled tab label
- Add active/paused toggle states
- Add run stats strings (last run, run count, never run, next run)
- Add toast messages for pause/resume/delete
- Add kebab menu labels (edit, delete)
- Add delete confirmation modal strings
- Add empty state strings (title, description, cta)
- Add day formatting strings (weekdays, weekend, everyday, individual days)
- Add schedule format string

* feat(04-03): create useScheduleDisplay hook for schedule formatting

- Add bitmask constants matching Go model (Sun=1, Mon=2, etc.)
- formatDaysOfWeek: smart groupings (Every day, Weekdays, Weekends) or comma-separated
- formatTimeOfDay: locale-appropriate 12/24hr time from HH:MM
- formatSchedule: combines days and time with i18n format string
- formatNextRun: smart relative formatting (Today, Tomorrow, Day name, Date)
- formatLastRun: formatted date or 'Never run'
- formatRunCount: pluralized run count

* feat(04-03): create ScheduledRecapItem component

- Render card with title and schedule pattern subtitle
- Show next run time when schedule is active
- Toggle between Active/Paused states with pause/resume actions
- Run stats (last run, run count) appear on hover
- Kebab menu with Edit and Delete options
- Delete confirmation modal with FormattedMessage
- Use useScheduleDisplay hook for all formatting

* feat(04-03): add ScheduledRecapItem styles

- Card with border, radius, and hover state
- Flexbox layout with title/subtitle and actions
- Title with truncation (ellipsis) for long names
- Subtitle with metadata separator styling
- Run stats with opacity transition on hover
- Toggle button min-width for consistent sizing
- Kebab menu button hover state

* docs(04-03): complete ScheduledRecapItem component plan

Tasks completed: 4/4
- Add i18n strings for scheduled recap UI
- Create useScheduleDisplay hook for schedule formatting
- Create ScheduledRecapItem component
- Add ScheduledRecapItem styles

SUMMARY: .planning/phases/04-scheduled-tab/04-03-SUMMARY.md

* feat(04-04): create ScheduledRecapsEmptyState component

- Empty state displays when no scheduled recaps exist
- Shows illustration with icons, title, description
- CTA button to create first recap
- Supports disabled state when agents bridge is disabled

* feat(04-04): create ScheduledRecapsList component

- Renders empty state when no scheduled recaps exist
- Maps over scheduled recaps to render ScheduledRecapItem
- Passes edit and create handlers through to children

* feat(04-04): add Scheduled tab to main Recaps component

- Add Scheduled tab after Unread and Read tabs
- Fetch scheduled recaps on mount with getScheduledRecaps
- Display ScheduledRecapsList when on scheduled tab
- Wire up edit handler (opens create modal - Phase 5 adds pre-fill)
- Import scheduled_recap_item.scss for styling

* style(04-04): add SCSS styles for scheduled recaps

- Add .scheduled-recaps-list styles (flex column, centered, gap)
- Add .scheduled-recaps-empty-state styles (centered, illustration, text)
- Consistent with existing recap UI styling patterns

* docs(04-04): complete Scheduled tab integration plan

* docs(04): update STATE.md for phase 4 completion

* docs(04): complete Scheduled Tab phase

Phase 4: Scheduled Tab
- 4 plans executed across 4 waves
- 15/15 requirements verified
- Human verified UI works correctly

* docs(05): capture phase context

Phase 05: Enhanced Wizard
- Implementation decisions documented
- Phase boundary established

* docs(05): add research hints for component discovery

* docs(05): research phase domain for enhanced wizard

Phase 05: Enhanced Wizard - Frontend Implementation
- Standard stack identified (existing codebase components)
- Architecture patterns documented (multi-step modal, bitmask days)
- Pitfalls catalogued (timezone, validation, edit mode)
- Code examples from codebase referenced

* docs(05): create phase plan for enhanced wizard

Phase 05: Frontend - Enhanced Wizard
- 6 plans in 3 waves
- Wave 1: Redux actions, DayOfWeekSelector
- Wave 2: ScheduleConfiguration, Run once toggle
- Wave 3: Modal integration, Edit wiring
- Ready for execution

* feat(05-01): add action type constants for create/update scheduled recap

- CREATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE
- UPDATE_SCHEDULED_RECAP_REQUEST/SUCCESS/FAILURE

* feat(05-02): create DayOfWeekSelector component

- Bitmask-based day selection matching server model
- Monday-first ordering for work schedule intuition
- XOR toggle for clean state management
- aria-pressed accessibility support

* feat(05-01): add createScheduledRecap async action

- Takes ScheduledRecapInput parameter
- Calls Client4.createScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing pauseScheduledRecap pattern

* feat(05-02): add DayOfWeekSelector styles

- Flexbox layout with 8px gap between buttons
- 40x40px day buttons with hover states
- Selected state uses button-bg color
- Error and disabled state styling

* feat(05-01): add updateScheduledRecap async action

- Takes id and ScheduledRecapInput parameters
- Calls Client4.updateScheduledRecap
- Dispatches RECEIVED_SCHEDULED_RECAP on success
- Follows existing action patterns

* docs(05-02): complete DayOfWeekSelector plan

Tasks completed: 2/2
- DayOfWeekSelector component with bitmask state
- Styled button group with toggle/hover/error states

SUMMARY: .planning/phases/05-enhanced-wizard/05-02-SUMMARY.md

* docs(05-01): complete Redux actions for scheduled recaps plan

Tasks completed: 3/3
- Add action type constants for create/update scheduled recap
- Add createScheduledRecap async action
- Add updateScheduledRecap async action

SUMMARY: .planning/phases/05-enhanced-wizard/05-01-SUMMARY.md

* feat(05-04): add run once toggle to RecapConfiguration

- Add runOnce, setRunOnce, and isEditMode props to Props type
- Import Toggle component
- Add run once toggle section at bottom of Step 1
- Toggle hidden when isEditMode is true
- Include descriptive text below toggle

* feat(05-03): create ScheduleConfiguration component for Step 3

- Add day-of-week selection using DayOfWeekSelector
- Add time picker with 30-minute intervals and locale-aware formatting
- Add time period dropdown (Previous day, Last 3 days, Last 7 days)
- Add custom instructions textarea with 500 char limit
- Add next run preview with timezone support
- Use getCurrentTimezone selector for user timezone

* feat(05-04): add run once toggle styles

- Add .run-once-group with top separator border
- Style toggle and label with proper alignment
- Add description text with left margin for alignment
- Use consistent spacing and typography

* feat(05-03): add Step 3 schedule configuration styles

- Add step-three layout with vertical flex and gap
- Add form-group styling with label and error states
- Add next-run-preview styling with background and subtle text
- Add textarea overrides for custom instructions input

* docs(05-04): complete run once toggle plan

Tasks completed: 2/2
- Add run once toggle to RecapConfiguration
- Add run once toggle styles

SUMMARY: .planning/phases/05-enhanced-wizard/05-04-SUMMARY.md

* docs(05-03): complete ScheduleConfiguration plan

Tasks completed: 2/2
- Create ScheduleConfiguration component
- Add Step 3 styles to SCSS

SUMMARY: .planning/phases/05-enhanced-wizard/05-03-SUMMARY.md

* feat(05-05): add schedule state and edit mode props to modal

- Add editScheduledRecap prop for edit mode detection
- Add schedule state (daysOfWeek, timeOfDay, timePeriod, customInstructions)
- Add runOnce state and validation state (daysError, timeError)
- Add useEffect to pre-fill form in edit mode
- Import createScheduledRecap, updateScheduledRecap actions
- Import ScheduleConfiguration component and getCurrentTimezone selector

* feat(05-06): update handleEditScheduledRecap to pass scheduled recap to modal

- Find scheduled recap by ID from scheduledRecaps array
- Pass editScheduledRecap via dialogProps to CreateRecapModal
- Early return if scheduled recap not found

* feat(05-05): update step navigation for run once and schedule flows

- Update handleNext to clear validation errors on navigation
- Update handlePrevious to clear validation errors on navigation
- Update getTotalSteps for run once (2-3 steps) vs scheduled (always 3)
- Update getActualStep for proper step indicator mapping

* feat(05-05): update renderStep for schedule vs run once flows

- Pass runOnce, setRunOnce, and isEditMode props to RecapConfiguration
- Show ChannelSummary for run once mode at step 3
- Show ScheduleConfiguration for scheduled mode at step 3
- Pass all schedule state props to ScheduleConfiguration component

* feat(05-05): update handleSubmit for immediate and scheduled recaps

- Add schedule field validation for non-run-once mode
- Dispatch createRecap for run once mode (existing behavior)
- Dispatch updateScheduledRecap for edit mode
- Dispatch createScheduledRecap for new scheduled recaps
- Navigate to ?tab=scheduled after creating/editing scheduled recap
- Add proper error messages for schedule validation failures

* feat(05-05): update modal header and button text for edit mode

- Update canProceed to validate schedule fields in step 3
- Add getConfirmButtonText helper for context-aware button text
- Show 'Start recap' for run once, 'Save changes' for edit mode
- Show 'Create schedule' for new scheduled recaps
- Update headerText to show 'Edit your recap' in edit mode

* docs(05-05): complete wizard integration plan

Tasks completed: 5/5
- Add schedule state and edit mode props
- Update step navigation for run once and schedule flows
- Update renderStep for schedule vs run once flows
- Update handleSubmit for immediate and scheduled recaps
- Update modal header and button text for edit mode

SUMMARY: .planning/phases/05-enhanced-wizard/05-05-SUMMARY.md

* fix(05-06): JSON.stringify body in scheduled recap API calls

createScheduledRecap and updateScheduledRecap were passing objects
directly to doFetch body, causing '[object Object]' to be sent instead
of JSON. Fixed to match createRecap pattern.

* fix(05-06): align time period values with server model

Frontend was using 'last_3_days' and 'last_7_days' but server expects
'last_24h', 'last_week', and 'since_last_read'. Updated options to match.

* fix(05-06): remove duplicate border on custom instructions textarea

- GenericModal adds a border to all .form-control elements
- Input widget's Input_fieldset already provides a border container
- This caused a double-border visual glitch on the textarea
- Added border: none to the inner textarea to fix the issue

* fix(05-06): reserve space for next run preview to prevent modal height jump

- Always render next-run-preview container (previously conditionally rendered)
- Use visibility:hidden instead of not rendering when no preview available
- Add non-breaking space placeholder to maintain consistent element height
- Prevents jarring visual jump when user selects a day of the week

* fix(05-06): remove border/background from next-run-preview

- Remove padding, border-radius, and background-color from .next-run-preview
- Style as plain text with subtle color and smaller font size
- Keep margin for appropriate spacing from time selector

* fix(05-06): use abbreviated timezone in next recap preview

- Use Intl.DateTimeFormat with timeZoneName: 'short' to get timezone
  abbreviation (e.g., EST, PST, EDT) instead of full label
- Remove unused getCurrentTimezoneLabel selector import
- Preview now shows 'Monday at 9:00 AM (EST)' instead of
  'Monday at 9:00 AM ((UTC-05:00) Eastern Time (US & Canada))'

* fix(05-06): prevent modal height jump when next run preview appears

- Move next-run-preview inside time-selection-group as helper text
- Add min-height: 16px to reserve space when preview is hidden
- Use visibility: hidden instead of display: none for consistent height
- Reduce step-three gap from 20px to 16px for better spacing
- Add margin-bottom: 0 to form-group to override default spacing

* fix(05-06): add section titles and fix spacing in schedule configuration

- Add section titles per Figma design (Heading 100 style):
  - 'When would you like your summary sent?' as main header
  - 'On which days should your recap run?' for days section
  - 'At what time?' for time section
  - 'Select a time period for your recap to cover' for time period
  - 'Additional instructions for {agentName}' for custom instructions
- Pass agentName prop from parent to show selected agent name
- Fix spacing: reserve space for next-run preview with container
  to prevent time period section from jumping when preview appears
- Update SCSS with schedule-section groups and proper spacing

* fix(05-06): remove duplicate title and fix subtitle-dropdown spacing

- Remove 'When would you like your summary sent?' duplicate title
- Add scoped CSS rule for 12px total spacing between subtitle and dropdown

* fix(05-06): use standard Toggle without text labels for active/paused state

- Remove onText/offText props from Toggle component
- Add ariaLabel for accessibility (describes toggle state and action)
- Update SCSS to remove min-width constraint that was for text display
- Navigation to scheduled tab after creating scheduled recap already works correctly

* fix(05-06): toggle color and tab navigation after creating scheduled recap

- Use btn-toggle-primary class for scheduled recap toggle to display proper button-bg color
- Add useQuery hook to read tab query parameter from URL
- Sync activeTab state with URL tab parameter to enable navigation after modal close

* fix(05-06): sync tab state with URL bidirectionally

- Add handleTabChange callback that updates both state and URL
- Use history.replace() to update URL without polluting browser history
- Remove tab param from URL when switching to 'unread' (default tab)
- Simplify URL sync useEffect to always update from tabParam
- This enables proper navigation after creating scheduled recaps

* fix(05-06): fix navigation to scheduled tab after creating scheduled recap

- Replace useRouteMatch() with getCurrentRelativeTeamUrl selector
- Modal was using route match which returned wrong URL context (modal is rendered at root level)
- Use team selector to get correct team URL for navigation
- Update test to remove unnecessary useRouteMatch mock

* fix(05-06): sort scheduled recaps by newest first

- Update getAllScheduledRecaps selector to sort by create_at descending
- Follows same pattern as other recap selectors (getUnreadRecaps, getReadRecaps)
- Derived selectors (getActiveScheduledRecaps, getPausedScheduledRecaps) inherit sort order

* docs(05-06): complete edit wiring and UI polish plan

* docs(phase-05): complete Enhanced Wizard phase

Phase 5: Enhanced Wizard
- 6 plans executed across 3 waves
- 13 requirements verified
- Multi-step wizard for creating/editing scheduled recaps
- Run once and scheduled flows
- Full edit mode with pre-fill
- Extensive UI polish based on human feedback

All 39 requirements complete. Milestone ready for audit.

* docs(v1): milestone audit complete - all requirements satisfied

- 39/39 requirements verified
- 5/5 phases passed
- 100% cross-phase integration
- 5/5 E2E flows complete
- 2 minor tech debt items (non-blocking)

* chore: remove .planning from git tracking

- Add .planning to .gitignore
- Remove .planning files from git index (kept locally)
- Planning files are for local development only

* feat(06-01): add AIRecapSettings and RecapLimitSettings structs

- RecapLimitSettings with 7 limit fields (recaps/day, scheduled, channels, posts, tokens, posts/day, cooldown)
- AIRecapSettings with master toggle and per-limit enforcement toggles
- SetDefaults methods with sensible defaults (10 recaps/day, 5 scheduled, etc.)
- isValid/IsValid validation methods enforcing natural minimums

* feat(06-01): integrate AIRecapSettings into Config struct

- Add AIRecapSettings field to Config struct
- Call AIRecapSettings.SetDefaults() in Config.SetDefaults()
- Call AIRecapSettings.IsValid() in Config.IsValid()

* test(06-01): add tests for AIRecapSettings and RecapLimitSettings

- TestAIRecapSettingsSetDefaults: verifies all defaults match spec
- TestRecapLimitSettingsValidation: verifies validation rejects invalid values
- TestAIRecapSettingsPreservesExistingValues: verifies SetDefaults preserves existing
- TestAIRecapSettingsIsValid: verifies IsValid delegates to DefaultLimits

* feat(06-02): create EffectiveRecapLimits struct

- Add EffectiveRecapLimits struct with 7 resolved limit fields
- Add LimitSource type with system/group/user constants
- Add UnlimitedValue constant (-1) for disabled limits
- Add IsLimitEnabled helper function for enforcement code

* feat(06-02): create GetEffectiveLimits resolution function

- Add GetEffectiveLimits(userID) returning resolved limits for any user
- Resolve limits from AIRecapSettings.DefaultLimits config
- Apply per-limit enforcement toggles (disabled = -1 unlimited)
- Add helper functions getValueOrDefault and getBoolOrDefault
- Structure for Phase 8 group/user resolution with TODOs

* test(06-02): add tests for GetEffectiveLimits function

- TestGetEffectiveLimitsDefaults verifies system defaults returned
- TestGetEffectiveLimitsWithDisabledToggle verifies -1 returned for disabled limits
- TestGetEffectiveLimitsWithCustomDefaults verifies custom config honored
- TestGetEffectiveLimitsAllTogglesDisabled verifies all -1 when all disabled
- TestGetEffectiveLimitsUnlimitedConfigValue verifies -1 config value honored
- TestIsLimitEnabled verifies helper correctly identifies enabled limits

* feat(07-01): add RecapStatusSkipped constant and SkipReason field

- Add RecapStatusSkipped constant for recaps skipped due to limit violations
- Add SkipReasonDailyLimit and SkipReasonCooldown skip reason constants
- Add ScheduledRecapId field to Recap struct for tracking scheduled recaps
- Add SkipReason field to Recap struct for tracking why recap was skipped
- Update Auditable() method to include new fields
- Update recapColumns and recapToMap to include new fields

* feat(07-01): add store interface methods for limit enforcement

- Add CountForUserSince to RecapStore for daily limit enforcement
- Add GetLastCompletedManualRecap to RecapStore for cooldown checking
- Add CountForUser to ScheduledRecapStore for max scheduled recaps limit
- Update mock implementations for both stores

* feat(07-01): implement store methods in SQL stores

- Implement CountForUser in SqlScheduledRecapStore
  - Counts active (non-deleted, enabled) scheduled recaps for a user
- Implement CountForUserSince in SqlRecapStore
  - Counts recaps since timestamp, excluding skipped recaps
- Implement GetLastCompletedManualRecap in SqlRecapStore
  - Returns most recent completed manual recap (no ScheduledRecapId)
  - Returns nil, nil when no manual recap exists

* feat(07-03): add daily limit check to scheduled recap worker

- Add GetEffectiveLimits and GetUser to AppIface for limit checking
- Check MaxRecapsPerDay before executing scheduled recap
- Create skipped recap record when daily limit exceeded
- Use user's timezone for midnight calculation
- Update next run time even when skipping (scheduler moves on)

* feat(07-03): add cooldown check to manual recap creation

- Check CooldownMinutes before allowing manual recap creation
- Return HTTP 429 with retry-after info when cooldown active
- Only checks against completed manual recaps (per CONTEXT.md)
- Failed recaps don't consume cooldown (checks completed only)

* feat(07-03): add i18n messages for cooldown errors

- Add cooldown_active error with retry info template
- Add cooldown_check_failed error message
- Uses "Your organization's policy limits..." pattern

* feat(07-02): add limit checks to CreateScheduledRecap

- Add max scheduled recaps limit check using CountForUser store method
- Add max channels per recap limit check against ChannelIds length
- Return HTTP 400 with clear error messages when limits exceeded
- Uses GetEffectiveLimits for limit resolution (ENF-01, ENF-02, ENF-08)

* feat(07-02): add i18n error messages for scheduled recap limits

- Add max_scheduled_reached message with "Your organization's policy limits..." pattern
- Add max_channels_exceeded message with limit and requested count
- Add count_failed internal error message

* test(07-04): add unit tests for post/token truncation

- Test proportional post distribution across channels
- Test minimum 1 post per channel guarantee
- Test empty channel handling
- Test token estimation (4 chars/token heuristic)
- Test token limit truncation removes from largest channels

Verifies ENF-05, ENF-06 truncation implementation.

* test(07-05): add ENF-07 permission preservation tests

- Add tests verifying over-limit users can view/edit/delete existing recaps
- Tests confirm management operations do NOT check limits (grandfathering)
- Tests confirm creation IS still blocked when over limit
- Add migration 000151 for missing ScheduledRecapId/SkipReason columns

ENF-07: Limits only block creation, not management of existing resources

* feat(08-01): create UnlimitedNumberSetting component

- Number input with Unlimited checkbox for admin console settings
- When checked: disables input and sets value to -1 (unlimited)
- When unchecked: enables input and sets value to defaultValue
- Supports disabled state and setByEnv footer

* test(08-01): add unit tests for UnlimitedNumberSetting

- Tests rendering with numeric and unlimited values
- Tests checkbox toggle behavior (check/uncheck)
- Tests number input changes
- Tests disabled state and setByEnv footer
- Tests custom unlimited label and placeholder
- 11 test cases covering core functionality

* feat(08-02): add Recaps subsection to admin_definition.tsx

- Import UnlimitedNumberSetting component
- Add 'recaps' subsection under site configuration section
- Include master enable toggle for AI Recap Limits
- Add 3 grouped sections: Quota Limits, Content Limits, Time Limits
- Configure all 7 limit settings with proper config keys
- Add AIRecapSettings and RecapLimitSettings TypeScript types
- All settings disabled when master toggle is off

* feat(08-02): add i18n strings for Recaps admin section

- Add admin.sidebar.recaps for navigation
- Add admin.site.recaps for section title
- Add admin.recaps.enable.* for master toggle
- Add admin.recaps.sections.* for section descriptions
- Add admin.recaps.max*.* for all limit field labels/descriptions
- Add admin.recaps.cooldownMinutes.* for time limit settings
- Add admin.recaps.unlimited for checkbox label
- Total: 24 new i18n strings

* style(08-02): remove section comments to fix lint errors

Remove inline comments that triggered lines-around-comment lint rule

* feat(09-01): add RecapLimitStatus model and App layer logic

- Add RecapLimitStatus, DailyUsageStatus, CooldownStatus structs
- Implement App.GetRecapLimitStatus with daily usage count and cooldown calculation

* feat(09-01): add GET /api/v4/recaps/limit_status endpoint

- Register route and handler
- Return structured limit status
- Add error translation

* feat(09-01): add TypeScript types for limit status

- Export RecapLimitStatus and related types

* feat(09-02): add recap limit status redux integration

- Add Client4.getRecapLimitStatus method
- Add Redux action, reducer, and selector for limit status
- Update GlobalState and initial state to include limitStatus

* fix(09-02): update CreateRecapModal error handling and tests

- Check dispatch result.error instead of try/catch to handle server errors
- Display server error message (e.g. policy limits) inline
- Fix TypeScript errors: displayName property and missing props in tests

* feat(09-03): implement user-facing limit status UI

* Verify Phase 9: User-Facing UX

* Remove UAT artifact

* fix: UI/UX issues (badge, toggle, input, tooltip)

* fix: Increase RecapUsageBadge tooltip z-index

* blank lines

* [MM-67163] checkpoint: scheduled recaps feature working

All phases (01-09) complete and verified:
- Database foundation with DST-aware scheduling
- API layer for CRUD operations
- Job scheduler and worker
- Scheduled tab UI with list/create/edit/delete
- Enhanced wizard with schedule configuration
- Config settings and admin console section
- Limit enforcement (daily, cooldown, token, post)
- User-facing limit status badge

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

* [MM-67163] simplify: reduce duplication and fix issues across scheduled recaps

- Extract requireScheduledRecapOwnership helper for 5 API handlers
- Consolidate ResumeScheduledRecap from 4 store calls to 2
- Remove redundant Get() in PauseScheduledRecap
- Extract advanceSchedule helper in worker to deduplicate skip/success paths
- Remove double PreSave() in store Save method
- Remove dead code fallback in GetEffectiveLimits
- Deduplicate ScheduledRecapInput construction in create modal
- Fix missing fetchRecapLimitStatus import (TS error)
- Extract day-of-week bitmask constants to @mattermost/types/recaps
- Fix hardcoded English strings in schedule_display formatNextRun

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

* Address CodeRabbit review feedback on scheduled recaps

Server fixes:
- Gate getRecapLimitStatus with requireRecapsEnabled guard
- Normalize error mapping (404 vs 500) in scheduled recap handlers
- Enforce MaxChannelsPerRecap limit in UpdateScheduledRecap
- Return explicit error for unsupported all_unreads mode in scheduled recaps
- Add compensation logic to clean up orphan recaps on job creation failure
- Fix proportional post truncation to strictly enforce maxPosts cap
- Add missing CountForUser retry wrapper in RetryLayerScheduledRecapStore
- Handle NULL ScheduledRecapId in manual recap cooldown lookup
- Exclude soft-deleted rows in scheduled recap Get query

Frontend fixes:
- Pass isCreationBlocked to ScheduledRecapsList empty state
- Fix same-day nextRunAt mislabeled as "Tomorrow" in schedule display
- Handle thunk error results in scheduled recap item actions
- Replace scheduled recaps map on full refresh instead of merging

Made-with: Cursor

* Handle thunk error results in scheduled recap toggle handler

Check dispatch result for errors in handleToggle to prevent
false-success UI flows when pause/resume operations fail.

Made-with: Cursor

* Fix CI failures: mock store, permissions, migrations, lint, and Playwright config

- Add ScheduledRecap() to storetest.Store mock and retrylayer test setup
- Register sysconsole_read_ai_recaps / sysconsole_write_ai_recaps permissions
- Renumber scheduled_recaps migration from 150→156 and recap_skip_fields from 151→157 to resolve version conflicts
- Fix ESLint errors in recap components (operator-linebreak, import order, labels, headers, etc.)
- Add AIRecapSettings to Playwright default_config.ts

Made-with: Cursor

* Address CodeRabbit Round 2 review feedback

- Add OpenAPI spec for GET /api/v4/recaps/limit_status with schema
  definitions for RecapLimitStatus, EffectiveRecapLimits,
  DailyUsageStatus, and CooldownStatus
- Set ScheduledRecapId when creating recaps from schedules to prevent
  cooldown logic from treating scheduled recaps as manual
- Handle past nextRunAt timestamps in schedule display: show "Yesterday"
  for -1 day and full date for older past dates

Made-with: Cursor

* Add missing variable declarations for AI recaps permissions

Made-with: Cursor

* Fix jsx-max-props-per-line lint errors in schedule_configuration.tsx

Made-with: Cursor

* Fix stylelint property order in recap SCSS files

Made-with: Cursor

* Update admin sidebar snapshots to include Recaps section

Made-with: Cursor

* Fix Go lint issues and add OpenAPI specs for scheduled_recap endpoints

- Use max/min builtins instead of if-statements (modernize/minmax)
- Use range-over-int syntax for for-loops (modernize/rangeint)
- Fix tautological Monday&Monday test (staticcheck/SA4000)
- Add OpenAPI specs for all 7 scheduled_recap API routes
- Add ScheduledRecap model definition to definitions.yaml

Made-with: Cursor

* Regenerate i18n en.json for recap-related strings

Made-with: Cursor

* Fix gofmt indentation in recap.go

Made-with: Cursor

* Fix scheduled recap CI regressions

Align the scheduled recap soft-delete store test with the current Get behavior and add the missing scheduled recap i18n strings so server and enterprise checks stay in sync.

Made-with: Cursor

* Address CodeRabbit review feedback on scheduled recaps

- Use session user ID instead of client-controlled recap.UserId for
  limit enforcement in UpdateScheduledRecap (security hardening)
- Add missing i18n entry for app.recap.fetch_posts.app_error
- All other review comments were already addressed in prior commits

Made-with: Cursor

* Fix schedule configuration import order

Reorder the moment import so the webapp lint job passes again on the scheduled recap PR.

Made-with: Cursor

* Allow selecting "all unreads" recap type when no current unreads exist

With scheduled recaps, users should be able to select "all unreads" even
without current unread channels since unreads will exist when the schedule
runs. The "run once" toggle is now disabled when all unreads is selected
with no current unreads, preserving the pre-scheduling behavior of
preventing an immediate recap with nothing to summarize.

Made-with: Cursor

* Fix indentation in recap_configuration.tsx to satisfy eslint

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

* Fix Recaps test mocks for scheduled recap state.

Keep the webapp test shard green by mirroring the new selectors and mount-time actions used by the Recaps page.

Made-with: Cursor

* Update server/channels/app/recap.go

* Update server/channels/app/recap.go

* Update server/channels/app/recap_limits.go

* Update server/channels/app/recap_limits.go

* Update server/channels/app/scheduled_recap.go

* Update server/channels/app/scheduled_recap.go

* Update webapp/channels/src/components/recaps/scheduled_recaps_empty_state.tsx

* Update server/channels/app/scheduled_recap.go

* Fix gofmt formatting in recap limits

Made-with: Cursor

* Fix recap limits for soft-deleted recaps

Keep deleted recaps in quota and cooldown checks so soft deletion cannot bypass AI usage enforcement.

Made-with: Cursor

* Fix translation

* Fix server check-style: concurrent indexes and lint cleanups

- Use CREATE/DROP INDEX CONCURRENTLY in 000168 scheduled recaps migrations
  (required by mattermost-govet concurrentIndex check).
- gofmt validation constants in scheduled_recap.go.
- Replace string += loops in recap tests with strings.Repeat for modernize linter.

Made-with: Cursor

* Fix 000168 migration: run CONCURRENTLY indexes outside transaction

PostgreSQL rejects CREATE/DROP INDEX CONCURRENTLY inside a transaction.
Morph requires -- morph:nontransactional for these migrations, matching
other index migrations in the repo.

Made-with: Cursor

* Stabilize scheduled Recaps for review

Bring the scheduled Recaps work back into a shippable state by tightening backend scheduling and limit semantics, cleaning up the UI flows, and adding focused Recaps E2E coverage.

Made-with: Cursor

* Fix scheduled recaps lint failures

Made-with: Cursor

* Fix scheduled recap Go lint

Made-with: Cursor

* Fix scheduled recaps Playwright check

Made-with: Cursor

* Sync scheduled recaps i18n catalog

Made-with: Cursor

* Fix server recaps CI checks

Made-with: Cursor

* Stabilize recap server CI setup

Made-with: Cursor

* Recaps: enforce token limit, remove dead truncation code, simplify scheduled worker

- Delete unused multi-channel truncation subsystem (FetchAndTruncatePostsForRecap, truncatePostsProportionally)
- Enforce MaxTokensPerRecap in the live recap path (previously a silent no-op)
- Drop redundant non-atomic daily-limit pre-check in the scheduled worker; rely on the atomic check in CreateRecapFromSchedule
- Disable non-recurring schedules on all terminal paths (including daily-limit skips) via finalizeSchedule
- Trim scheduled-recap job payload to scheduled_recap_id
- Exclude skipped recaps from GetRecapsForUser

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps webapp: format schedule times in the schedule timezone, static day i18n, typed schedule fields

- Display next-run and schedule times using the scheduled recap's timezone (shared schedule_time_format helper) instead of browser-local time
- Replace dynamic day-of-week i18n message IDs with static descriptors so strings are extractable/translatable
- Add ScheduledRecapTimePeriod/ScheduledRecapChannelMode union types
- Use shared Button in the scheduled recaps empty state

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps e2e: cover scheduled edit/delete/all-unreads/empty-state and token-limit enforcement

- Add UI coverage for editing, deleting, and the empty state of scheduled recaps
- Add all-unreads scheduled recap modal flow
- Add immediate-recap token-limit enforcement tests (single and per-channel), validating MaxTokensPerRecap truncation end-to-end
- Extend recaps page object and helpers

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: regenerate store layers/mocks and i18n after cleanup; fix lint

- Regenerate timerlayer/retrylayer/RecapStore mock to canonical order
- Drop orphaned app.recap.fetch_posts.app_error i18n key (removed with dead fetch helper)
- Add missing semicolon in UnlimitedNumberSetting props

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: regenerate default roles permissions and admin sidebar snapshot

- Regenerate Cypress default_roles_permissions fixture to include AI Recaps sysconsole permissions
- Update admin sidebar snapshot for the new Recaps section

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: stabilize flaky cooldown round-up test

The cooldown round-up subtest placed the prior recap 30s before a 2-minute
cooldown, leaving only ~30s of slack before the rounded remaining time would
flip from 2 to 1 minute. Under heavily loaded CI this could intermittently fail
the exact-minute assertion. Move the prior recap to 1s ago so the remaining time
sits near the top of the 2-minute band (~59s slack) while still exercising
ceiling rounding.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: store ScheduledRecap.ChannelIds as jsonb

Postgres is the only supported database, so the prior TEXT+JSON-string
workaround for MySQL compatibility is unnecessary. Store ChannelIds in a
jsonb column and type the model field as model.StringArray, which removes the
bespoke marshal/unmarshal and intermediate scan struct in the store.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: gate recap limit settings on the ai_recaps permission

Add access:"ai_recaps" to DefaultLimits and all RecapLimitSettings fields so a
delegated admin with sysconsole_write_ai_recaps can save the limit values,
instead of falling back to requiring manage_system.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: drop bespoke Users row lock for limit enforcement

The recap limit savers were the only SELECT ... FOR UPDATE in the sqlstore.
Conform to the prevailing pattern (e.g. channel_bookmark_store.Save): enforce
MaxScheduledRecaps / MaxRecapsPerDay with a transactional count + insert and no
row lock, accepting the same best-effort behavior under concurrency as channels,
team members, and bookmarks.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: document SaveOnceByTypeAndData dedup semantics

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: use SERIALIZABLE isolation for limit-check inserts

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: mark orphaned recap skipped when job enqueue fails

When CreateJob fails after the recap row is committed, flag the recap
skipped with reason job_creation_failed instead of leaving it pending.
Skipped recaps are excluded from the daily-limit count, so this frees
the quota slot for a recap that will never run, and keeps CreateRecap
consistent with CreateRecapFromSchedule.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retrigger CI (flaky enterprise npm cache EEXIST)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retrigger CI (flaky Vet API container init)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Recaps: re-check channel read permission at recap execution time

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* fix scheduled recap job server test setup

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retry flaky webapp test

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* ci: retry documentation impact review

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM-67163: Address review feedback: remove unused userID param, return AppError from GetRecapLimitStatus

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* MM-67163: Bulk channel permission check for recap creation, bounded by channel limit

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* chore: rerun CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* chore: rerun CI after network failure

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-21 13:10:25 -04:00
2851af059d Add admin-locked profile fields for email users and pre-provisioned names on invites (#37458)
* Add TeamSettings.LockProfileFieldsForEmailUsers with server-side enforcement

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add API tests for LockProfileFieldsForEmailUsers enforcement

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Hide admin-locked profile fields in user settings and add System Console dropdown

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Support pre-set username and name on team email invites

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add tests for invite profiles; fix resend worker channel-list parsing

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add pre-set profile inputs to member invite modal

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Prefill and lock pre-set username on signup page

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add first/last name editing to System Console user detail and document new setting

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix lint issues in invite modal profile inputs

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix double outline on invite modal profile inputs inside GenericModal

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Refactor invite emails to InviteEmailData struct and harden invite profile validation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Centralize profile-lock permission exemption in app layer and add config coverage

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Per-field name locking in profile settings, typed lock setting, and shared invite profile helpers

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Add Playwright E2E coverage for locked profile fields and pre-set invite profiles

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Sync playwright package-lock with merged workspace versions

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Restore upstream playwright package-lock (fix npm ci drift)

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Assert invite input cleared instead of chip text after adding email

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix invite modal scroll, username error layout, and clipped autocomplete

Keep the footer pinned while tall profile rows scroll, show username
validation full-width after blur, and portal select menus so they are not
clipped by the scroll container.

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

* fixes for autocomplete items not aligning properly

* Make invite autocomplete menu portal opt-in and fix modal chrome

Confine document.body menu portaling to the invite modal via a menuPortal
prop, and restore click-away, slide-in animation, and header alignment for
the scrolling invite modal layout.

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

* Fix eslint lines-around-comment on menuPortal props

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

* Fix stylelint property order in invitation modal SCSS.

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

* Fix stylelint property order in invitation_modal.scss

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Adapt invite modal E2E to portaled autocomplete menus

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Simplify locked profile invite implementation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix invite modal review and E2E feedback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky enterprise CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry Docker image export CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Minimize locked profile fields diff

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix locked profile E2E documentation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Address minimized test review feedback

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Fix email test whitespace

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry OpenSearch download CI

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky Cypress thread navigation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Preserve legacy invite behavior without profiles

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Clarify invite profile validation

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

* Retry flaky enterprise E2E

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matthew Birtch <2040554+matthewbirtch@users.noreply.github.com>
2026-07-17 14:29:44 -04:00
Pablo VélezandMattermost Build 3a820143a1 MM 69100 - Team ABAC Membership - members sync and end user surfaces (#37054)
* MM-69063 - Add team ABAC model and constants foundation

* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration

* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and  GetTeamMembersToRemove store

* Enforce team membership ABAC on join and hide policy governed teams from  non-qualifying users in the directory

* Add team_ids to access policy assign/unassign, expose per-team policy GET,  and support abac_match_only for not_in_team user listing

* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users

* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings

* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id  validation, export, and audit cleanup

* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete

* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts

* Enhance access control policy tests to include team policy search alongside channel policy search

* Add team membership access control feature flag to docker-compose generation

* Implement team access control policy checks and refactor related components

* MM-69100 - add team membership ABAC sync worker with mode-aware removal and auto-add

* Add team ABAC removal/auto-add notifications, cascade audit, and team custom-rules save backend

* Add Team Settings team membership tab with custom-rules editor, auto-add toggle, save confirmation, and mode-flip  sync trigger

* Add team access control policy panels, job details team list, and e2e coverage for membership tab and discoverability cards

* ix save double-submit with loading state, team privacy via updateTeamPrivacy, mode-flip count accuracy, sync trigger logic, i18n keys, and e2e test cleanup

* Add team membership policy notices to invite and add-to-team flows, team access control system messages, and a team policy attributes endpoint

* Add team membership recommended tag for qualifying users and team requirements notice in the members modal

* temp

* adjust styling, add new e2e, fix team admin job permissions issues

* e2e clean items created

* fix directory and team integration e2e tests

* Surface team sync results in the Sync Job Details modal via a Teams tab linked from the chained channel job

* code clean up, adjust styling

* Add access control attributes endpoint and enhance E2E tests for team membership

* Enhance team access control by updating permission checks and syncing jobs

* Add API-level enforcement gate tests for ABAC team membership

* Fix formatting in team channel settings component by adding missing semicolon

* Fix formatting in Client4 class by adjusting type annotations for clarity

* revert unwanted changes in package-lock

* Refactor job permission test and simplify error handling in team details component

* Update test assertions and enhance auto-add functionality in team membership tab

* Update onboarding tests to use 'Public Team' card for team access settings

* Refactor AccessSettings and TeamPolicyEditor components to replace allowOpenInviteCheckbox with public and private team buttons, and update access control job dispatching logic.

* Remove redundant state updates for channel assignments in access control policy reducer

* Implement email suppression for team membership notifications and add related tests

* Authorize ABAC team self-join by attribute match instead of the join_private_teams role

* comments clean up

* Gate team privacy type normalization behind active ABAC, keep legacy allow_open_invite-only path  otherwise

* adjust job details styles

* Add team membership policy disconnect confirmation, fix linked-policy affected-member count, and polish job details modal

* Combine custom rules with system policy expressions for accurate confirm counts

* Implement child resource count stamping and deletion gating for access control policies

* Enhance team sync job triggering logic for membership changes without auto-add

* Refactor team privacy handling to align with open-directory model; update comments for clarity on allow_open_invite logic.

* Deduplicate team ABAC sync jobs per policy to prevent concurrent runs emitting duplicate DMs and audit records on HA clusters

* fix linter warnings

* Add confirmation for policy removal in TeamDetails tests

* Update delete policy message to include Teams in the warning

* Add linked teams warning to policy deletion and update translations

* Add Auto-add feature for policy selection and team management

- Enhanced PolicySelectionModal to include an Auto-add checkbox for each policy.
- Updated PolicyList to manage Auto-add state and reflect changes in the UI.
- Modified TeamAccessControl to handle Auto-add functionality and persist changes.
- Added tests to ensure Auto-add behavior works as expected in various scenarios.
- Updated translations for Auto-add related strings.

* Add team membership sync footer and enhance job fetching with policy ID

* Refactor team membership policy handling and sync footer implementation

* Add team sync channel cascade test for private channel membership

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-17 09:55:36 +03:00
Ben SchumacherandClaude Sonnet 5 cce485f605 [MM-69561] Add ability to rotate (regenerate) Personal Access Tokens (#37295)
* MM-69561: Add ability to rotate (regenerate) Personal Access Tokens

- Add UpdateTokenRotate to UserAccessTokenStore interface and implement
  in sqlstore: deletes sessions on the old secret, then updates the
  token row with the new secret and expiry in one transaction
- Regenerate store retrylayer, timerlayer, and mocks
- Add RotateUserAccessToken app method: validates expiry (bot-exempt),
  captures old session for cache eviction, generates new secret, and
  sends a notification email
- Register POST /api/v4/users/tokens/rotate handler with full permission
  checks (create_user_access_token + edit_other_users + manage_system
  for sysadmin targets); rejects OAuth sessions and disabled tokens
- Add RotateUserAccessToken to the Go client (client4.go)
- Add storetest covering secret rotation and old-session cleanup
- Add API4 tests: happy path, permission denials, OAuth rejection, and
  max-lifetime enforcement

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

* MM-69561: Add dedicated rotate email and i18n strings

- Add SendUserAccessTokenRotatedEmail (subject/body distinct from the
  'added' email so users aren't confused by a rotation event)
- Add SendUserAccessTokenRotatedEmail to ServiceInterface + mock
- Add en.json strings for the rotate email and the two new error ids
  (rotate.app_error, disabled_token.app_error)
- Switch RotateUserAccessToken to call SendUserAccessTokenRotatedEmail

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

* MM-69561: Add mmctl token rotate subcommand

- Add RotateUserAccessToken to the mmctl Client interface and mock
- Add 'mmctl token rotate <token-id> [--expires-in <duration>]' command
  reusing the existing resolveTokenExpiry/parseExpiresIn helpers from
  'generate'; prints the new secret once on success
- Add unit tests: happy path, --expires-in passed through, server error,
  invalid --expires-in

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

* MM-69561: Regenerate mmctl docs for token rotate

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

* MM-69561: Add API docs for POST /users/tokens/rotate

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

* MM-69561: Fix i18n string ordering after extract

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

* MM-69561: Add missing API4 test cases for token rotate

Cover the three untested access-control branches flagged by the test
analysis bot:
- Rotating a disabled token returns 400
- Non-system-admin rotating a sysadmin's token returns 403
- Rotating a remote user's token returns 403

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

* MM-69561: Address review comments

- Fix handler authorization order: check SessionHasPermissionToUserOrBot
  and manage_system before IsRemote/IsActive to avoid leaking token state
  to unauthorized callers; matches revokeUserAccessToken/disableUserAccessToken
- Fix API docs minimum server version: 10.8 -> 10.10

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

* MM-69561: Restore i18n strings accidentally deleted by extract

The earlier i18n-extract run stripped ~164 unrelated translation keys
(mostly enterprise-only strings like app.pap.* and api.ldap.*) because
the enterprise codebase isn't present in this checkout, so the
extractor treated them as unused. Restore them while keeping the 5
new keys added for token rotation.

* comment

* [MM-69561] Add webapp Regenerate option for Personal Access Tokens

Adds a "Regenerate" link to Account Settings > Security > Personal
Access Tokens that calls the POST /users/tokens/rotate endpoint added
in the server-side rotate PAT work. Regenerating shows a confirmation
modal naming the token, then reveals the new secret via the existing
one-time-copy flow used for token creation.

- webapp Client4.rotateUserAccessToken
- mattermost-redux rotateUserAccessToken action
- Regenerate link/confirm modal/reveal flow in user_access_token_section
- i18n strings
- Playwright e2e coverage

* Fix regenerate PAT e2e test: confirm modal is not nested in the Profile dialog

ConfirmModal renders via react-bootstrap's Modal, which portals to
document.body as a sibling of the Profile dialog rather than a
descendant, so it must be located via #confirmModal on the page
instead of scoped to the Profile dialog locator.

* Let users pick a new expiry when regenerating a Personal Access Token

Previously, regenerating a token always called rotateUserAccessToken
with no expiresAt, so the rotated secret never expired even if the
original token did. The Regenerate confirmation modal now includes the
same expiry picker used by token creation (extracted into a shared
renderExpiryPicker helper), enforces MaximumPersonalAccessTokenLifetimeDays
the same way, and disables the confirm button until a valid expiry is
selected.

* Scope the red background in the Regenerate modal to the warning text only

The confirmation question, expiry picker, and its hints were sitting
inside the same alert-danger box as the warning, making the whole
modal read as an error. Only the warning paragraph keeps the red
background now.

* Move the regenerate confirmation question below the expiry picker

* Align rotate-token wording with UI: use 'regenerate/regenerated'

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

* [MM-69561] rename pat_expiry_notify job to notify_expiring_access_tokens

Unifies naming with the sibling cleanup_expired_access_tokens job and
fixes the "expiry" vs "expiring" ambiguity: this job warns about tokens
approaching expiry, not ones that have already expired. Safe to rename
outright since the job hasn't shipped yet.

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

* [MM-69561] remove dead session lookup from EnableUserAccessToken

The GetSessionContext call and its result were never used: both branches
returned nil regardless. Leftover from mirroring DisableUserAccessToken's
shape, which does use its session (to revoke it) unlike Enable.

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

* [MM-69561] rename remaining PAT identifiers to match AccessToken convention

The job-level rename (pat_expiry_notify -> notify_expiring_access_tokens)
left the app-layer function and its helpers using the old PAT/
PersonalAccessToken naming. Rename them to match:

- NotifyPersonalAccessTokensExpiring -> NotifyExpiringAccessTokens
- patExpiryBucket -> accessTokenExpiryBucket
- sendPATExpiryNotification -> sendAccessTokenExpiryNotification
- patExpiryNotifyBatchLimit -> expiringAccessTokenBatchLimit
- patExpiryThresholds -> expiringAccessTokenThresholds
- maxPersonalAccessTokenExpiry -> maxUserAccessTokenExpiry

Also reword the package doc on notify_expiring_access_tokens, which no
longer needs to explain a PAT/UserAccessToken naming split now that the
app-layer method matches the job name.

Pure rename, no behavior change.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 23:08:19 +02:00
af49e8dc80 Preserve 429/503 retry status codes through DoActionRequest (#36700)
* Preserve 429/503 retry status codes through DoActionRequest

DoActionRequest collapsed all plugin non-200 responses to 400, losing
the retry semantics carried by 429 and 503 (RFC 6585, RFC 7231). This
preserves 429/503 verbatim, maps other 5xx to 502 Bad Gateway, and
leaves other non-200 responses wrapped as 400.

* update api documentation for new errors

* fix tests affected by change

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
2026-07-10 12:19:25 -06:00
Scott BishelandScott Bishel 0d7ae8e58d Add file upload element to interactive dialogs (#36881)
* Add file upload element to interactive dialogs

  Adds a new file element type to interactive dialogs, letting users
  upload files in a dialog and forward the file IDs to the integration.

  Server:
  - model: new file DialogElement type with validation, AllowMultiple
    field, and SubmitDialogRequest.FileIds.
  - SubmitInteractiveDialog validates submitted file IDs (existence +
    ownership) from both file_ids and any file IDs referenced in
    submission values; bounded and batched.
  - client4.getFileInfo / Client4.GetFileInfo.

  Webapp:
  - AppsFormFileUpload component: upload, progress, removal, and
    hydration of pre-set file IDs; single vs allow_multiple selection.
  - Wired into the dialog -> apps-form conversion (file field type).
  - Submit is blocked while any field has an upload in progress
    (per-field pending tracking).

  Tests:
  - Go unit tests for model + submit-time file-ID validation.
  - Jest tests for the upload component.
  - Cypress e2e spec (file_upload_spec.js) + webhook fixtures.

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

* coderabbit review fixes, linter fixes

* update openAPI spec for file upload

* revert changes to package-lock.json

* review fixes, add correct ids to E2Etests

* fix dryrun security issue

* lint fixes

* Address dialog file upload review feedback and UI file cap

* test fixes

* add several unit tests

* lint fix

---------

Co-authored-by: Scott Bishel <sbishel@ScottsFderalMac.home.local>
2026-07-09 21:44:53 -06:00
Scott BishelandMattermost Build ca25611b0c MM-69219: Add multiple concurrent dialogs via action_button element type (#37119)
* Add multiple concurrent dialogs support with action_button element type

  Enable plugins to open child dialogs from within an existing dialog via a new
  action_button dialog element and POST /actions/dialogs/execute endpoint. Parent
  dialogs stay open while children stack (cap of 3). Dialog handlers derive the
  team permission and forwarded team from the server-loaded channel, not the
  client-supplied TeamId.

* update docs, code rabbit review fixes

* update comment per coderabbit

* fix tests

* increase test coverage

* fix bad merge

* Replace PostActionAPIResponse with ExecuteDialogActionResponse; replace single-dialog Redux slot with dialogs map

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 17:03:14 -06:00
Scott BishelandMattermost Build a6060f5d05 MM-68754: Add silent post delivery for bots and integrations (#36771)
* MM-68754: Add silent post delivery for bots and integrations

  Bot, OAuth, incoming webhook, and plugin posts can request silent
  delivery via ?silent=true (REST), silent:true (webhook payload), or
  silent_notification:true (plugin Props). Silent posts are visible in
  the channel but produce no notifications, unread, or New Messages
  line. force_notification overrides silent. Non-integration senders
  requesting silent receive HTTP 403.

  Also tightens SanitizeProps to strip from_webhook, from_bot,
  from_oauth_app, from_plugin, force_notification, and
  silent_notification from client input. These props are now set
  server-side based on session and entry-point flags, preventing
  identity spoofing. Legitimate callers see no behavior change since
  the server already controlled these props at the response shape;
  the hardening only affects callers attempting to forge them.

* Drop redundant prop-bag silent_notification path from webhooks

* Address CodeRabbit feedback + rebase test fixup

* Update tests to use CreatePostFlags.FromIncomingWebhook instead of forging from_webhook prop

* update api documentation

* test fixes

* added additional tests

* review fixes, update comments

* code review changes, fix test

* fix formatting issues

* fix lint errors

* avoid backward compatibility issues, by no longer stripping the  Post properties.
will add them back in v12 when breaking changes are allowed

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-09 17:02:22 -06:00
Ben SchumacherandClaude Sonnet 5 ef60931ab9 [MM-69075] Revoke non-compliant personal access tokens (#37030)
* [MM-68421] Frontend: PAT creation UI — expiry picker and status display

Wires the webapp UI to the server-side support added in PR #36706 (and
the MM-68419 model changes):

- Extends UserAccessToken type with expires_at, and ClientConfig with
  EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays.
- Threads expires_at through Client4.createUserAccessToken and the
  mattermost-redux createUserAccessToken action.
- Adds a date/time picker to the PAT creation form (reuses
  DateTimePickerModal). Honors enforcement and clamps to the
  max-lifetime setting; maps server error ids
  (expires_at_required/in_past/too_far) to localized messages.
- Displays expiry, derived status badge (active/expired/disabled), and
  an approaching-expiry warning (<7 days) in the account settings token
  list. Mirrors expiry + status in the admin Manage Tokens modal.
- Adds the supporting i18n keys.

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

* [MM-68421] Address review: i18n, ServerError type, scss, explicit guards

- Inject intl so the "Expires in N days" tooltip and the picker
  ariaLabel are localized instead of hard-coded English.
- Use the canonical ServerError type from @mattermost/types/errors
  instead of an ad-hoc inline cast.
- Dedupe the new i18n ids — keep a single "Expires: " string per
  namespace and drop the duplicates introduced in the first pass.
- Add scss for setting-box__token-expiry / __token-status /
  __token-expiry-warning so the new status pill and warning render
  with the expected styling.
- Replace truthy checks on the numeric expiresAt with explicit
  > 0 comparisons for readability.

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

* [MM-68421] Replace expiry datetime picker with preset chooser + custom date

Per UX discussion: PATs are long-lived so sub-day precision is noise.
Swap the DateTimePickerModal for a native <select> of presets
(No expiry, 7d, 30d, 90d, 1 year, Custom date) with a date-only
<input type="date"> revealed when Custom is selected. Effective time
is end-of-local-day, which matches how users think about expiry.

- Drops the DateTimePickerModal import and the picker open/close
  handlers; removes the moment-timezone import.
- Adds isPresetAllowed() that hides presets exceeding
  MaximumPersonalAccessTokenLifetimeDays, and a defaultExpiryPreset()
  that picks 30d (then 7d, then Custom) when enforcement is on,
  No expiry otherwise.
- The custom <input type="date"> uses min=today and max=now+maxDays
  for native bounds; submit-time validation still maps server error
  ids if the user bypasses the bounds.
- i18n: adds preset labels; drops the now-unused picker/clear/change
  strings.

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

* [MM-68421] Fix CI: playwright config and ESLint blank line

- Add EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays
  to e2e-tests/playwright/lib/src/server/default_config.ts so its tsc -b
  matches the updated ServiceSettings shape.
- Drop a stray double blank line in user_access_token_section.tsx that
  tripped no-multiple-empty-lines.

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

* [MM-68421] Sort i18n keys to match formatjs extract output

ci/i18n-extract diffs en.json against the formatjs extractor's sorted
output and fails on any difference. Re-run the extract so the new
PAT-expiry keys land in alphabetical position.

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

* [MM-68421] Address CodeRabbit findings

- Clamp the default custom-expiry date to maxLifetimeDays when set
  so the form doesn't open in an invalid state when
  defaultExpiryPreset() falls back to 'custom'.
- Reject a cleared/empty custom date with expires_at_required
  instead of silently submitting without an expiry; only forward
  expiresAt to the action when it's > 0.
- Use an explicit undefined check in Client4.createUserAccessToken
  so an intentional 0 isn't dropped from the request body.

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

* [MM-68421] Update manage_tokens_modal snapshot for expiry + status row

The admin token list now renders an Expires row and a status badge per
token; refresh the jest snapshot.

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

* [MM-68421] Add unit tests for PAT expiry helpers

Per the PR Test Analysis advisory: export the pure helpers from
user_access_token_section.tsx and add unit tests covering them.

Coverage:
- deriveTokenStatus: active / expired / inactive branches.
- mapServerErrorIdToMessage: all three server error ids (short and
  api.user.create_user_access_token.*.app_error variants) and the
  default null path.
- endOfLocalDayPlusDays: end-of-day on the Nth future day, 0 days.
- endOfLocalDayFromIsoDate: valid ISO date and malformed inputs.
- PRESET_DAYS: snapshot of the preset durations.

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

* [MM-68421] Freeze time in PAT helper tests to avoid midnight flake

The date arithmetic helpers (Date.now, new Date()) could disagree
across a midnight boundary, making the tests theoretically flaky.
Pin system time to a stable mid-day in 2026 via jest.useFakeTimers.

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

* [MM-68421] Add component tests for PAT expiry creation UI

Covers the create-form validation branches (missing description, empty
custom date, past date, beyond maxLifetimeDays), enforceExpiry rendering
(no-expiry option hidden + enforced hint), maxLifetimeDays preset
filtering, and token-list status display (active/expired/disabled, never,
and the "expires soon" warning).

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

* [MM-68421] Add behavioral status/expiry tests for manage_tokens_modal

Covers the admin token modal's derived status display: Active + "Never"
for an active token without expiry, Expired for an active token past its
expiry, Disabled for an inactive token regardless of expiry, and Active
with a rendered date (not "Never") for a token expiring in the future.

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

* [MM-68421] Align PAT expiry UI with shipped server contract

The server (#36706) did not ship a separate EnforcePersonalAccessTokenExpiry
flag — expiry enforcement is implied by MaximumPersonalAccessTokenLifetimeDays
> 0. Two webapp gaps surfaced once the server side merged:

- enforceExpiry was read from the never-sent config.EnforcePersonalAccessToken
  Expiry, so the "hide No-expiry / require expiry" path was dead. Derive it from
  maxLifetimeDays > 0 instead and drop the dead config flag from the component,
  redux props, ClientConfig/ServiceSettings types, and the e2e default config.
- The server returns app.user_access_token.expires_at_{required,in_past,too_far}
  .app_error, but mapServerErrorIdToMessage matched the api.user.create_user_
  access_token.* namespace, so the localized errors never fired. Map the actual
  ids.

Unit tests updated accordingly.

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

* [MM-68421] Add Playwright e2e for PAT expiry UI

Covers, against a real server, the personal access token expiry surfaces in
Account Settings > Security:

- the expiry picker renders all presets and reveals the custom-date input
- a custom expiry with no date is blocked client-side
- MaximumPersonalAccessTokenLifetimeDays > 0 hides "No expiry" and oversized
  presets, shows the enforced hint, and rejects an over-the-limit custom date
- the token list shows Active/Never, an "expires in N days" warning, and the
  Disabled badge (seeded via the API)

The expired-status badge is left to the component unit tests since the server
rejects creating a token whose expiry is already in the past.

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

* [MM-68421] Use locator('option') for native select assertions

getByRole('option') does not reliably match the options of a closed native
<select>, which would make the absence assertions (toHaveCount(0)) pass
vacuously. Query option elements by DOM instead, matching the repo's house
pattern for native selects.

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

* [MM-68421] Fix expiry overshoot vs server cap and review nits

Review found that presets/custom dates resolve to end-of-local-day, which can
sit up to ~24h beyond the server cap of "now + MaximumPersonalAccessTokenLife
timeDays" (an exact duration from creation time). With a max configured, the
default preset equals the cap, so accepting the default and saving was rejected
server-side with expires_at_too_far for most of the day.

- Clamp the submitted expiry to the server cap when a maximum lifetime is set.
  Validation still runs on the raw end-of-day value so an explicitly out-of-range
  custom date is still rejected; only the in-range end-of-day overshoot is clamped.
- Count "expires in N days" from the start of today and floor it, so an end-of-day
  expiry no longer over-reports by one (a 7-day token reads "7 days", not "8").
- Add an aria-label to the custom expiry date input.

Tests: unit coverage for clampExpiresAtToMaxLifetime; a Playwright spec that
creates a token with the default preset under a 30-day cap and asserts success.

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

* [MM-68421] Apply prettier formatting to PAT e2e spec

The ci/playwright/npm-check job (lint + prettier + tsc + lint:test-docs) failed
on prettier formatting. eslint and tsc were clean; reformat the spec to satisfy
prettier as well.

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

* [MM-68421] Add PAT max-lifetime System Console setting and creation-form UX fixes

- Add ServiceSettings.MaximumPersonalAccessTokenLifetimeDays number field to
  System Console > Integrations > Integration Management, after Enable Personal
  Access Tokens (disabled when tokens are disabled).
- Disable the token creation Save button until a non-empty description is
  entered (description input is now controlled; whitespace-only rejected).
- Render the token creation form as a distinct "Create New Token" card so it no
  longer blends into the existing token list.

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

* Fix stylelint property order in new-token card

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

* [MM-68421] Validate PAT expiry inline before submit

Surface the expiry validation error in the create-token form and disable
Save while the selection is invalid, instead of only failing inside the
create-confirmation flow. Previously a system admin had to click Save then
"Yes, Create" before seeing "An expiry date is required." for an empty
custom date.

Extracts the expiry checks into getExpiryValidationError(), reuses it as
the handleCreateToken guard, and renders the result inline + in the Save
button's disabled condition.

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

* [MM-68421] Fix PAT expiry e2e specs for inline validation

The "blocks submitting a custom expiry with no date chosen" and "enforces
expiry when a maximum lifetime is configured" specs clicked the Save button
and expected an inline error afterward. Since 7ccd65ea surfaces the expiry
error inline and disables Save while the selection is invalid, the click
timed out on a disabled button.

Assert the inline error is visible and that Save is disabled, instead of
clicking it.

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

* MM-69075: server-side revoke non-compliant PATs

Adds store queries (GetNonCompliantExpiry/CountNonCompliantExpiry),
app methods to count and bulk-revoke (hard-delete) non-compliant PATs in
batches with session-cache invalidation, sysadmin-gated api4 endpoints
with audit logging, Client4 methods, and the OpenAPI spec.

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

* MM-69075: System Console button to revoke non-compliant PATs

Adds a 'Revoke non-compliant tokens' control under Integrations >
Integration Management (next to Maximum Personal Access Token Lifetime).
It shows the current non-compliant count (refreshed on load, save, and
revoke), disables when there is nothing to revoke, and confirms the
irreversible delete with the blast radius. Wires up the TS Client4
methods and i18n strings.

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

* MM-69075: Address PR review feedback

- Rename route from /tokens/revoke_non_compliant to /tokens/non_compliant/revoke for consistency with /tokens/non_compliant/count
- Remove redundant c.LogAudit("") before permission check
- Remove redundant c.LogAudit on success (structured audit record is sufficient)

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

* MM-69075: Simplify non-compliant token revocation with single store call

- Add DeleteNonCompliantExpiry store method: atomically deletes non-compliant
  tokens and their sessions in a single Postgres CTE, returning affected user
  IDs for session cache clearing
- Replace the get->extract IDs->delete dance in RevokeNonCompliantUserAccessTokens
  with the new single store call per batch
- Switch post-loop partial completion check to CountNonCompliantExpiry
- Add partial completion error i18n string
- Clear stale error banner in refreshCount on successful fetch

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

* MM-69075: Remove GetNonCompliantExpiry, migrate tests to DeleteNonCompliantExpiry

GetNonCompliantExpiry is now unused — DeleteNonCompliantExpiry supersedes it.
Remove it from the store interface, sqlstore, retrylayer, timerlayer, and mock.
Migrate the store test to exercise DeleteNonCompliantExpiry instead, adding
session-deletion verification.

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

* MM-69075: Fix DISTINCT undercounting bug and add multi-token-per-user test

- Remove SELECT DISTINCT from DeleteNonCompliantExpiry CTE so each deleted
  token row is returned, not collapsed per user; totalRevoked now counts
  tokens, not users, and batch-continuation is correct
- Deduplicate userIDs in app layer before ClearSessionCacheForUser calls
- Add multi-token-per-user fixture to store test: two non-compliant tokens
  sharing a UserId verify len(userIDs)==4 and catch any future DISTINCT regression

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

* MM-69075: Fix generated store layers and mock ordering

Regenerate retrylayer/timerlayer via make store-layers and fix
DeleteNonCompliantExpiry alphabetical position in mock file.

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

* MM-69075: Inline nonCompliantExpiryWhere into its sole caller

The helper was extracted to share the predicate between GetNonCompliantExpiry
and CountNonCompliantExpiry. GetNonCompliantExpiry is gone; with a single
call site the extracted function adds no value.

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

* MM-69075: Fix flaky test — relax global count assertions

Global count assertions (require.Equal against baseline) are fragile when
other concurrent tests hold non-compliant tokens. Replace exact equality
with GreaterOrEqual/LessOrEqual for global counts. The DISTINCT regression
is still caught precisely: sharedUserID must appear exactly twice in the
returned slice (once per token, not once per user).

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

* MM-69075: Fix false partial-revoke error caused by read-replica lag

After DeleteNonCompliantExpiry writes to master, the post-delete
CountNonCompliantExpiry read targets the replica, which may not have
caught up yet. This made the first revoke call return a spurious HTTP
500 even though all tokens were actually deleted, and a second click
then showed 'Revoked 0' because nothing remained.

Fix: track whether the batch loop exited via a natural break (all done)
vs. exhausted revokeNonCompliantMaxBatches (genuinely incomplete), and
signal the partial-revoke error only in the latter case. This removes
the racy replica read entirely — the loop's own exit conditions prove
completion on master.

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

* MM-69075: Add Playwright e2e coverage for revoke non-compliant tokens UI

Covers the gap flagged in PR review (#37030): button/disabled states,
AlertBanner states, confirmation modal open/cancel/confirm, count
refresh after policy save, and token auth invalidation (compliant and
bot tokens survive a revoke).

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

* MM-69075: Fix testid for number-type Maximum PAT Lifetime field

Number-type TextSetting inputs use ${id}number as their test id, not
${id}input (only text-type inputs get the 'input' suffix). Found by
actually running the spec locally against a server built from this
branch - all 4 tests now pass.

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

* MM-69075: Fix prettier formatting in e2e spec

CI's prettier --check flagged this file; ran prettier --write to match
project style. The other CI lint warnings (max-lines, no-warning-comments)
are pre-existing, in files this branch never touched.

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

* MM-69075: Fix TS2345 - UserAccessToken.token is optional

The token secret field is only populated on the object returned from
CreateUserAccessToken, so its type is string | undefined. Guard for
that in tokenIsUsable instead of asserting non-null at every call site.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-02 18:19:40 +02:00
Jesse Hallam 78decd39ce chore: bump Go version to 1.26.4 (#37287) 2026-07-02 16:58:40 +03:00
Miguel de la CruzandMiguel de la Cruz 1eb4c62cf9 PSAv2 endpoint improvements (#36782)
* Updates search opts and cursor for searching property fields

* Adds store changes and tests to accomodate the new search options

* Add an index for improving delta query efficiency

* Enhances the get property fields endpoint to support deltas and hierarchical searches

* Adds new fields search endpoint

* Adds since to the property values endpoints

* Adds property value endpoint documentation

* Fix linter

* Address coderabbit comments

* Complete i18n

* Minor improvements

* Consistently apply DWIM semantics around object_type system

* Add support for DM/GMs

* Move auditRec to the top of the searchPropertyFieldsCore method

* Update since mechanics to use greater or equal

* Update API docs

* Updated tests to filter after the semantics changes

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
2026-06-22 17:27:02 +02:00
Felipe Martin c45a675553 MM-68976 Preserve PluginSettings.SignaturePublicKeyFiles on config patch endpoint (#36868)
* MM-68976 Preserve PluginSettings.SignaturePublicKeyFiles on config patch endpoint

The full PUT /api/v4/config endpoint silently preserves
PluginSettings.SignaturePublicKeyFiles (added in #13682), but the sparse
PUT /api/v4/config/patch endpoint had no equivalent guard, so a session
with sysconsole_write_plugins could modify the field through it.

Mirror the full update endpoint's behavior by silently preserving the
existing value in patchConfig, and add a regression test equivalent to
the one in TestUpdateConfig.

* MM-68976 Document SignaturePublicKeyFiles as non-modifiable in config API spec

Both the update and patch config endpoints preserve
PluginSettings.SignaturePublicKeyFiles; note this in the OpenAPI
descriptions alongside the existing PluginSettings.EnableUploads note.
2026-06-15 15:09:05 +02:00
Pablo VélezandMattermost Build 4641761122 MM - 69063 - team abac backend and security gate (#36903)
* MM-69063 - Add team ABAC model and constants foundation

* Add team ABAC store EXISTS, channel Type retrofit, policy count split, and index migration

* Add team ABAC app layer: access gate, hydrators, assign/unassign, cleanup, and  GetTeamMembersToRemove store

* Enforce team membership ABAC on join and hide policy governed teams from  non-qualifying users in the directory

* Add team_ids to access policy assign/unassign, expose per-team policy GET,  and support abac_match_only for not_in_team user listing

* Add team ABAC client methods, websocket handler, per-team System Console policy UI, and hide policy-governed teams from non-qualifying users

* Make team ABAC mode-aware: advisory on public teams, strict on private, and surface governed private teams to qualifying users in directory listings

* Flag-gate team ABAC mutation/read APIs and fix policy-save error handling, member-removal limit, team-id  validation, export, and audit cleanup

* coderabbit feedback; Broadcast team policy enforcement updates on policy create/update and activation, not only on delete

* Update team access control policy schema to allow nullable policies and enhance test cases with channel counts

* Enhance access control policy tests to include team policy search alongside channel policy search

* Add team membership access control feature flag to docker-compose generation

* Implement team access control policy checks and refactor related components

* Audit-log team ABAC policy removal on team archive and delete

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-12 23:39:16 +02:00
Elias NahumandJesse Hallam 93d0e6198d Route Calls pushes through a VoIP token if present (#36726)
* Route Calls pushes through a VoIP token if present

Add VoIP device id support so calls notifications can be delivered as iOS
PushKit pushes, while standard pushes continue using the regular device
token.

* New SessionPropVoIPDeviceId + IsValidVoIPDeviceID validator
* PushNotifyAppleReactNativeVoIP platform constant
* voip_device_id accepted on PUT /users/sessions/device and on login
* DoLogin refactored to accept LoginOptions struct; all four call sites
  (api4/user, channels/web/{saml,magic_link,oauth}) converted
* sendPushNotificationToAllSessions: when SubType=calls, prefer
  Session.Props[voip_device_id]; fall back to standard DeviceId

Tests cover the validator, the device-props endpoint (accept/reject), and
the SubType=calls routing matrix.

* Skip session per Calls plugin "answered_elsewhere" convention

When the Calls plugin fans out a cancel-ring push to a user's other
VoIP devices after they answer a call from one of them, it can't tell
core which session to skip because the public plugin API doesn't expose
a skip-session-id parameter. It encodes the auth-session-id on SenderId
together with Category="answered_elsewhere". Core honors the convention
and clears SenderId on the per-session copy before forwarding so the
session id never reaches the proxy or device. Category is preserved on
the wire so the mobile client can pick CXCallEndedReason.answeredElsewhere.

The matching side lives in mattermost-plugin-calls' push_notifications.go;
both sides cross-reference the constant.

* delay voip token audit until session props are set

* capture voip audit failed case

* Promote VoIP token to a column; dispatch by Transport; harden revocation

* ai feedback review

* feedback review

* feedback review

* fix tests

* update openAPI

* update serialized session model

---------

Co-authored-by: Jesse Hallam <jesse@mattermost.com>
2026-06-11 16:34:53 -03:00
Devin Binnie 684ddb32a9 [MM-68988][MM-68989][MM-68990][MM-68991][MM-68997][MM-68998] Session Attributes MVF - Server-work (#36934)
* [MM-68988][MM-68989][MM-68990][MM-68991][MM-68997] Session Attributes MVF - Server-work

* PR feedback

* [MM-68998] Add web app hooks for Desktop App to signal a refresh of attributes/manifest

* Fix types

* Adjust the test to test the license first

* PR feedback

* Coderabbit feedback

* More tests
2026-06-09 13:10:53 -04:00
Julien Tant 6ef5d58b7f Board channel bookmarks with target_id and readonly bookmark API (#36572)
Automatic Merge
2026-06-02 21:24:05 +02:00
Jesse Hallam 5283ca54b4 add PushNotification.Transport and related const (#36829)
* add PushNotification.Transport and related const

Supporting change for
https://github.com/mattermost/mattermost-plugin-calls/pull/1189,
originally in https://github.com/mattermost/mattermost/pull/36726.

* updated API docs to match
2026-06-01 14:12:44 +00:00
Ben SchumacherandClaude Opus 4.5 307452bb55 [MM-67113] Add license preview/diff view when uploading a new license (#34877)
* [MM-67113] Add license preview/diff view when uploading a new license

Add a preview step when uploading a new license file in the Admin Console,
allowing administrators to review differences before applying.

Backend:
- Add POST /api/v4/license/preview endpoint
- Add PreviewLicenseFile method to Go client

Frontend:
- Add previewLicense method to TypeScript client and Redux action
- Add LicenseDiffView component to display license comparison
- Refactor UploadLicenseModal to 3-step flow: loading → preview → success
- Fix License type field name (sku_short_name)
- Update date formatting on License details page for consistency
- For "entry" licenses, show only new license info (no comparison)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-01 11:34:23 +02:00
23d83b74d2 MM-65723 Validate user auth update requests (#36749)
* MM-65723 validate user auth updates

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

* MM-65723 assert auth update persistence

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

* MM-65723 move auth service allowlist into model

Extracts the allowlist used by isValidUpdateUserAuthRequest into a new
model.IsValidUserAuthService helper next to IsSSOUser/IsOAuthUser so the
auth service list lives next to its constants. Adds a unit test for the
helper.

* go mod tidy

Drops stale github.com/bep/imagemeta v0.12.0 entry left in go.sum.

* Address PR feedback: 2 items resolved, 1 declined

* Address PR feedback: 1 items resolved, 1 declined

* Address PR feedback: 1 items resolved, 3 declined

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Julien Tant <julien@craftyx.fr>
2026-05-28 09:30:44 +00:00
Alejandro García MontoroandMattermost Build ecd72f79bd MM-68787: Support sovereign-cloud endpoints for Azure Blob Storage (#36732)
* Support sovereign-cloud endpoints in the Azure Blob Storage backend

Adds an AzureCloud enum to FileBackendSettings and the matching
FileSettings / ExportFileSettings entries, defaulting to commercial.
The supported values pick the service URL the SDK signs against:

  - commercial: vhost-style against blob.core.windows.net, exactly
    the existing default. Only the storage account name is needed.
  - government: vhost-style against blob.core.usgovcloudapi.net, no
    user-supplied endpoint. Only the storage account name is needed.
  - custom: the admin-provided AzureEndpoint is the full service URL,
    scheme and storage account included, and Mattermost passes it to
    the SDK unchanged. Covers Azurite, reverse proxies, Azure China,
    and any future Azure cloud Mattermost has not codified a preset
    for.

Wires the new field through NewFileBackendSettingsFromConfig,
NewExportFileBackendSettingsFromConfig, ConfigToFileBackendSettings,
SetDefaults, the System Console (File Storage and Export Storage
panels) with matching i18n strings, and a dedicated Cypress spec.
Azurite test settings switch to AzureCloud=custom with the full
http://host:port/account/ URL so the existing integration suites
keep driving Azurite without any conditional URL plumbing inside the
driver. buildAzureServiceURL is now total: it returns an error for
custom-without-endpoint and for unknown enum values rather than
producing a malformed URL.

------
AI assisted commit

* Hide S3 File Storage fields when a different driver is selected

Match the existing Azure-field behavior: when the selected file storage
driver is not Amazon S3, hide the S3-only fields entirely instead of
just disabling them. Both driver groups already use this hide-when-
inactive pattern in the dedicated export storage section.

The Cypress assertion in azure_blob_storage_spec.js that codified the
old disable-only behavior is updated to expect not.exist.

------
AI assisted commit

* Address review feedback on AzureCloud validation and config-test endpoints

- Switch FileSettings.isValid AzureCloud / ExportAzureCloud value
  checks from slices.Contains to a switch statement, matching the
  closed-enum style used a few lines above for DriverName.
- Revert the io.EOF suppression in testFileStore and testEmail. The
  suppression was bot-suggested log-noise polish; reviewer prefers
  to keep the original error handling for those endpoints.
- Drop the redundant "http(s)" qualifier from the scheme check comment
  in buildAzureServiceURL.

------
AI assisted commit

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-26 16:34:48 +00:00
Alejandro García MontoroandMattermost Build c6b59cc9a7 MM-68663: Admin console support and Test Connection generalization for Azure Blob Storage (#36583)
* Generalize the file storage Test Connection endpoint

Replaces the S3-only /api/v4/file/s3_test handler with a backend-agnostic
POST /api/v4/file/test that validates mandatory fields per driver and
runs a write/read/delete probe against the configured backend. The
legacy /file/s3_test route stays as a thin wrapper so existing clients
keep working.

The driver switch validates S3 and Azure mandatory fields explicitly,
treats Local as a no-op (no required credentials), and rejects unknown
or empty driver names with a 400 and a specific error code so admins
get a useful message instead of a generic backend failure.

Reuses config.Desanitize (renamed from the package-private desanitize)
so the FakeSetting placeholder swap for secrets is shared with the
PUT /api/v4/config save path. Adding a new driver-secret in the future
only requires touching config.Desanitize once. Desanitize is also made
nil-safe on every pointer dereference so callers can hand it a partial
config without first running SetDefaults().

Mattermost-redux and the webapp client gain a corresponding
TestFileStoreConnection method that the admin console action layer
calls instead of the deprecated S3-specific method.

------
AI assisted commit

* Wire Azure Blob Storage into the file storage admin console

Adds the Azure Blob Storage option to the File Storage panel in the
System Console. Selecting it enables Azure-specific fields for the
storage account name, container, optional path prefix, shared key,
optional endpoint override, secure-connections toggle, and request
timeout. The fields are hidden and disabled when the driver is set to
Local or S3, matching the existing pattern.

Help text and placeholders are added in the webapp i18n catalog so
admins see the same field labels documented in the admin guide.

The same set of fields is repeated for the Files Export panel when
DedicatedExportStore is enabled, keeping the export backend
configurable independently of the primary file store.

------
AI assisted commit

* Document /api/v4/file/test in the OpenAPI spec

Adds the new backend-agnostic file storage Test Connection endpoint to
the public OpenAPI surface. The request body is optional: callers that
omit it test the running server configuration, callers that include a
full AdminConfig test the supplied configuration without persisting
anything. The deprecated /api/v4/file/s3_test endpoint is left
unchanged in the spec for the existing S3-only flow.

------
AI assisted commit

* Add UI-only Cypress coverage for the Azure file storage panel

Adds a Cypress spec that drives the System Console File Storage panel,
switches the driver to Azure Blob Storage, fills in the Azure fields,
and asserts the expected fields appear (and S3 fields are hidden). The
spec is UI-only and does not depend on an Azure backend or Azurite, so
it can run in CI without external infrastructure.

Updates the existing environment_spec.js so it tolerates the new Azure
option in the driver dropdown.

------
AI assisted commit

* Nil-guard file storage mandatory-field checks

CheckMandatoryS3Fields and CheckMandatoryAzureFields built a
FileBackendSettings via NewFileBackendSettingsFromConfig before
validating, but that constructor dereferences pointers
unconditionally and would panic if a caller skipped the api
handler's reflective nil check. Validate the required pointers
directly against FileSettings instead, dropping the throwaway
constructor call so the methods are safe to call from any path.

------
AI assisted commit

* Check permission before validating file settings

The /file/test handler ran checkHasNilFields before
SessionHasPermissionTo, so an unauthorized caller posting a partial
config got a 400, leaking config shape, rather than a 403. Swap the two
blocks so the permission decision happens first.

------
AI assisted commit

* Preserve FakeSetting when desanitize has no actual

The Azure access key, export Azure access key, and S3 secret access key
branches in Desanitize reassigned target to actual without checking
actual for nil. When the running config had no value, the FakeSetting
placeholder in target was replaced with nil, dropping the field from the
round-trip. Guard the assignment so the placeholder stays in place when
actual is unset.

------
AI assisted commit

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-25 11:36:02 +00:00
David Krauser e8632bd456 [MM-68777] Add admin property field permission level (#36558) 2026-05-22 11:01:41 -04:00
Ibrahim Serdar Acikgoz ba1cec51a5 [MM-68693] Resource level permission policies and new simulation (#36472) 2026-05-21 14:40:05 +02:00
Jesse Hallam 77f9ecdfde Upgrade Go to 1.26.3 and update deps in tool modules (#36658) 2026-05-20 17:25:49 -04:00
Daniel Espino García 92f6870a2b Add "last used" field for incoming webhooks (#36416)
* Add "last used" field for incoming webhooks

* Address feedback

* Rename migrations

* Fix web lint
2026-05-19 15:22:04 +02:00
Ben Cooke 02023f0328 [MM-68463] New endpoint to GET user by auth_data (#36352) 2026-05-15 15:26:03 -04:00
Julien TantandClaude Opus 4.6 323841e9c5 Add board channel types (BO/BP) for Integrated Boards (#35887)
* Add board channel types (BO/BP) with POST /boards API

Introduces board channel types as a new channel variant that reuses the
Channels table but is fully isolated from all /channels endpoints.

Model:
- Add ChannelTypeOpenBoard ("BO") and ChannelTypePrivateBoard ("BP")
- Add IsBoard(), IsOpenBoard(), IsPrivateBoard() helpers
- Add board-specific websocket events (board_created/updated/deleted/restored)

Store:
- SaveBoardChannel: atomic channel + view creation in a single transaction
- Save() rejects board types (forces use of SaveBoardChannel)
- Exclude boards from all channel listing/search queries (GetTeamChannels,
  GetAll, GetChannels, GetChannelsByUser, GetDeleted, autocomplete, search)

API:
- POST /boards: create board channel (feature-flagged behind IntegratedBoards)
- All /channels write endpoints reject board types with 400
- All /channels read endpoints reject or exclude board types
- Open boards get same public-read semantics as open channels

Tests:
- 15 rejection tests covering every /channels write + read endpoint
- 9 exclusion tests covering every listing/search endpoint
- 8 store tests for SaveBoardChannel + Save rejection
- 4 board creation API tests (create, private, flag off, sidebar exclusion)
- 3 authorization tests for board permission semantics

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

* Update generated files: i18n, go.mod, migrations list

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

* Add i18n translations for board channel error strings

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

* Fix board guard ordering in getChannelMembers and getChannelStats

Move the board rejection check after the permission check so that
nonexistent channel IDs still return 403 (not 404) matching the
original behavior expected by TestGetChannelMembers.

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

* Filter boards at store level instead of API guards

Store.Get() now excludes board types via WHERE clause, making boards
invisible to all /channels endpoints. Added GetBoardChannel() for
/boards endpoints. Removed redundant API-level rejectBoardChannel
guards from 10 handlers that already call GetChannel(). Kept explicit
guards only on 3 handlers that don't fetch the channel.

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

* Fix empty i18n translation for app.channel.save_member.app_error

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

* Add board system properties, kanban column config, and audit logging

Migration:
- Register "boards" property group with system-wide Assignee (user) and
  Status (select: Todo/In Progress/Complete) fields, both protected
- Idempotent migration following content flagging pattern

Board creation:
- Look up boards fields by name, set board:linked_properties on channel
- Build kanban view props with group_by mapping status options to columns
- Add typed KanbanProps/KanbanColumn/KanbanGroupBy structs with
  ToProps()/KanbanPropsFromProps() for round-tripping
- Add audit record logging for POST /boards
- Add early team_id validation in API handler
- Error on missing status options instead of silent empty columns

Tests:
- Migration test: field creation + idempotent re-run
- Board creation test: verify kanban props + linked_properties
- Fix updateChannelMemberRoles test to use valid role string

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

* Add boards migration mock to testlib store setup

The boards property migration calls System().GetByName() which needs
a matching mock expectation, same pattern as content_flagging_setup_done.

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

* Add kanban view props validation and tests

Validate kanban View.Props in IsValid(): group_by required with valid
field_id, 1-100 columns, each column needs id, name, and at least one
option_id. Update all test helpers to produce valid kanban props.

11 dedicated validation tests + round-trip test for KanbanProps.

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

* Validate board display name is not empty

Add early DisplayName validation in CreateBoardChannel with a clear
error. Add tests for empty and whitespace-only display names.

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

* Exclude boards from GetMany and getChannelsMemberCount

Add board type exclusion to Store.GetMany() and use filtered channel
IDs in getChannelsMemberCount handler so board channels don't leak
into member count results. Add test covering the endpoint.

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

* Fix review issues: drop search indexing for boards, use request context

- Remove search layer indexing of board channels so they stay invisible
  to Elasticsearch/Bleve-powered search and autocomplete
- Replace context.Background() with rctx.Context() for proper
  cancellation and tracing in CreateBoardChannel

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

* Fix gofmt alignment in websocket_message.go after merge

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

* Regenerate server i18n after merge

* Restore translation for permission_policy.app_error

* Filter board channels in name lookups, autocomplete, and indexing

The store-layer board exclusion filter was missing from getByName,
getByNames, GetDeletedByName, the global Autocomplete, and
GetChannelsBatchForIndexing — leaving boards reachable via name
lookups, the no-team-filter search path, and admin reindex jobs.

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

* Reject boards in id-batch lookups, unread, and member-mutation endpoints

- GetChannelsByIds, GetChannelsWithTeamDataByIds, and GetChannelUnread
  now exclude BO/BP at the store layer so boards can't slip through if
  callers stop filtering first.
- updateChannelMemberNotifyProps, updateChannelMemberAutotranslation,
  and viewChannel now reject board IDs explicitly via the existing
  rejectBoardChannelByID helper, matching the other write endpoints.

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

* Gate boards properties setup on the IntegratedBoards feature flag

doSetupBoardsProperties registered the boards property group and
fields at every server boot regardless of the IntegratedBoards
feature flag. Skip the migration when the flag is disabled so the
property metadata only appears once boards are actually enabled.

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

* Use App accessors for boards property lookups

CreateBoardChannel reached into a.Srv().PropertyService() directly
instead of going through the App-level GetPropertyGroup and
GetPropertyFieldByName methods that already wrap the service. Switch
to the standard App accessors so the calls match the rest of the
codebase.

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

* Log full channel input on createBoard audit record

createBoard only captured team_id and type on the audit record, so
failed creations lost most of the request payload. Use
AddEventParameterAuditableToAuditRec with the full channel struct,
matching createChannel.

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

* Use allow-list of message channel types in store filters

Inverted every sq.NotEq{[BO, BP]} filter into sq.Eq{messageChannelTypes}
(or teamMessageChannelTypes for queries that also exclude direct
channels) so that any future non-message channel type — wikis, etc. —
is excluded by default rather than requiring every existing call site
to be updated. Also rewrote GetChannelUnread on top of the squirrel
builder so the same allow-list slice can be reused.

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

* go.mod: promote prometheus/common to direct after merge

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

* Use model.NewPointer for boards property permission field

Drops the local permNone variable in doSetupBoardsProperties and uses
model.NewPointer(model.PermissionLevelNone) inline, matching the
surrounding ContentFlagging/ManagedCategory code.

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

* Extract saveViewT to share Views insert between Save and SaveBoardChannel

ViewStore.Save and SaveBoardChannel both built the same INSERT INTO
Views statement, so a future column addition would need updates in two
places. Extract the insert (plus PreSave/IsValid) into a private
saveViewT method that accepts any sqlxExecutor — the regular master
handle for ViewStore.Save, and the channel transaction for
SaveBoardChannel.

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

* Add IsMessageChannel helper on model.Channel

Mirrors IsBoard for the positive case: returns true for Open, Private,
Direct, and Group channel types. Lets future filtering code be
expressed against the allow-list rather than enumerating board types,
so newly introduced non-message channel types are excluded by default.

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

* Move board input validation into Channel.IsValidBoard

CreateBoardChannel inlined four guards for type, team, and display
name. Move the type/team_id/display_name checks into a new
Channel.IsValidBoard method so the rules live with the model and
return the AppError directly. The TrimSpace on DisplayName stays at
the call site to match how CreateChannel sanitizes before validating.

Drops the now-unused app.channel.create_board_channel.{invalid_type,
no_team,no_display_name} translations and adds matching
model.channel.is_valid_board.* keys.

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

* Extract buildBoardKanbanView from CreateBoardChannel

The kanban view construction (read status options, build columns,
serialize props, assemble *model.View) only depends on the status
property field and the creator id. Pulling it into its own helper
shrinks CreateBoardChannel and makes the column-building logic
testable in isolation.

Adds board_test.go with coverage for the empty-options error path,
the standard happy path, and the option-skipping branches.

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

* Test Channel.IsValidBoard

Cover the four reject cases (wrong type, missing team_id, empty
display name) plus the open and private board accept cases.

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

* gofmt board_test.go

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

* Document POST /api/v4/boards in OpenAPI spec

* Add valid kanban props to api4 makeTestViewForAPI helper

* Assert kanban.ToProps error in makeTestViewForAPI helper

The helper used to swallow the error from kanban.ToProps. Take *testing.T
and require.NoError so a serialization failure surfaces immediately at the
call site instead of producing a malformed view.

* Run boards properties setup unconditionally

The feature-flag gate added in 6298e15e86 made the test suite impossible:
test infra applies FeatureFlags overrides only after app.NewServer returns,
but doAppMigrations runs during NewServer, so the flag was always false at
migration time. The migration short-circuited, the boards property group
was never registered, and every CreateBoardChannel test 500'd with
"boards property group not found."

Drop the gate. The migration is idempotent (keyed in System) and benign —
matches doSetupContentFlaggingProperties and doSetupManagedCategoryProperties.
IntegratedBoards still gates route registration (api4/board.go) and the
CreateBoardChannel runtime entry (app/board.go), so the property group sits
unused until the feature is enabled.

* Add Client4.CreateBoard and use it in tests

Adds boardsRoute() and CreateBoard(ctx, channel) on Client4 mirroring
CreateChannel/CreateView. Refactors api4 board tests off the raw
DoAPIPost("/boards", ...) calls and the SaveBoardChannel store
inserts that predated the API; both now exercise the public client
method, drop the makeTestBoardView helper and the manual SaveMember
follow-ups, and route through setupBoardTest so IntegratedBoards is
enabled where needed.

* make modules-tidy

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-13 16:25:08 -07:00
d8612e378f [MM-2541] Shortcut to mark all channels as read for a team (#34012)
* feat(webapp): added keyboard shortcut for Mark All As Read (MM-2541)

- Added shortcut (within sidebar) for Shift+ESC to mark _all_ messages, teams as read
    - Desktop only
- Added feature toasts for new features and localStorage support
- Added feature toast for mark-all-as-read feature
    - Should decide when/how people want this shown, I just followed designs
    - Will only show if the user has not clicked 'Got it' before, and is not on mobile
- Added confirmation modal for mark all as read shortcut
    - Contains option to not show again, saved in localStorage
- Added English translations for read shortcut
    - Will need i18n aid on other languages

This is a draft version of this feature update that still needs testing and i18n support, along with a11y validation.

* feat(webapp): feature flags and fixes for mark all as read shortcut

- Added feature flags surrounding rollout of mark-all-as-read shortcut
- Added shortcut to list of shortcuts in help section
- Extended tests for new components
- Updated snapshot for sidebar_list, keyboard_shortcuts_modal
- Fixed styling and CSS issues

Still in draft, needs documentation and e2e support.

* fix(webapp): fixed some issues with new mark-all-read feature

- Scoped persistent storage to current user ID
  so that subsequent new logins also get the notification
- Replaced LocalStorage calls with useGlobalState calls, sad
  that I missed that this updated call was being used.
- Fixed an issue that would have caused the new shortcut to
  show up in the Help menu's shortcuts without being enabled.

* Fixed a snapshot test and a missing i18n member

* Replaced useGlobalState with backend-ready usePreference. Previous version was just a mistake as we didnt know about the supported API

* fix(server): fix lint issue with gofmt

* feat(server,webapp): added cleaner and more effective method with which to mark-all-read

- Added 2 new routes to the API (need to find docs to update those):
    - `PUT /api/v4/channels/members/<userId>/direct/read` will mark a user's non-team DMs and GMs as read
    - `PUT /api/v4/users/<userId>/teams/<teamId>/read` will do a similar action as the multi-channel mark_read action, but with a teamId signifier. Because this is using a teamId, it will _not_ handle DMs or GMs.
- Updated sidebar_list.tsx to use these new routes for the new shortcut
- Added extensive testing, including feature flag assurance.

* fix from upstream changes

* fix: eslint errors in teams actions

* document new API endpoints

* fix i18n

* fix err id

* remove unused localhost methods

* use ShortcutKey and ShortcutSequence

* feature_enhancements, mark as read toast enchancements

* read all modal mount point, use openModal

* use handler

* fix style

* fix: fix refactoring typo

* Merge fix: realign branch with upstream changes

Upstream MM-67319/MM-67320 (#36037) moved ShortcutKey and
WithTooltip into the shared package and rewrote the keyboard
shortcuts test to snapshot real DOM instead of a
react-test-renderer tree. The merge resolution missed several
follow-on consequences; clean them up so the branch builds, type
checks, lints, passes i18n-extract-check and runs without
throwing at mount.

- Port the inline-content variant from the deleted channels-side
  shortcut_key.scss to the new shared shortcut_key.css.
- Refresh the keyboard_shortcuts_sequence snapshot so it matches
  Testing Library's container output (DOM only, no component
  nodes, class= not className=).
- Repoint mark_all_as_read_modal and mark_all_as_read_toast at
  components/shortcut_key for ShortcutKeys and use
  ShortcutKeys.escape; the channels-side with_tooltip is now a
  thin re-export and the field was renamed in the shared keys
  map. Without this both consumers threw "Cannot read properties
  of undefined" at mount.
- Switch mark_all_as_read_toast's UserAgent import to
  @mattermost/shared/utils/user_agent; the channels-local
  utils/user_agent path no longer resolves.
- Drop the orphan mark_all_threads_as_read_modal.cancel string
  from en.json so formatjs extraction is in sync.

* Clean up TestReadAllInTeam

Drop four lines left from debugging and replace them with a real
assertion: LastViewedAtTimes must contain the test channel with a
value at or after the most recent post.

Update three client.GetChannel calls to the (ctx, id) signature;
the prior etag argument no longer compiles after upstream removed
it.

* Use SelectBuilder for team channels query

GetTeamChannelsWithUnreadAndMentions built a squirrel query and
then manually called ToSql before handing the string+args to
GetReplica().Select. SelectBuilder accepts the builder directly
and removes the intermediate dance, matching the pattern used
elsewhere in this store.

* Mark all team-channel threads on team read

MarkTeamChannelsAndThreadsViewed used Thread().MarkAllAsReadByTeam
unconditionally, writing every thread membership in the team for
the user even when nothing was stale. Scoping the call to
channelsToView (channels with unread channel-level messages) would
have closed the perf concern but introduced a regression: in CRT
mode a thread reply does not bump the channel's TotalMsgCount, so
a channel can be read at the channel level while still having
unread thread replies, and those would have been silently skipped.

Build the channel-id list from the keys of the times map instead.
GetTeamChannelsWithUnreadAndMentions already populates that map
for every team channel the user belongs to, so no extra query is
needed. MarkAllAsReadByChannels then filters the actual UPDATE
through its LastReplyAt > LastViewed clause, keeping writes
bounded to genuinely stale rows.

Gate the channel-level work (UpdateLastViewedAt, push clearing,
the MultipleChannelsViewed event) on channelsToView being
non-empty, but always run the thread mark and broadcast
ThreadReadChanged for every team channel so CRT clients refresh
thread state in channels that had no channel-level change.

* Mark mark-read audit records as success

The handlers for mark all DM/GM and mark team read created an
audit record with status Fail and never updated it on success,
so successful calls were always logged as failures.

* Mark all DM/GM threads on full read

MarkAllDirectAndGroupMessagesViewed early-returned when no
channel had unreads, so followed threads in DMs/GMs whose
channel-level counters were already current stayed unread under
CRT. Mirror MarkTeamChannelsAndThreadsViewed and call
MarkAllAsReadByChannels for every DM/GM in times.

* Polish DM/GM channels-with-unreads query

Use model.ChannelTypeDirect/Group constants instead of bare
"D"/"G" literals, and update the error wrap to mention DM/GM
channels (it was copied from the team variant).

* Fix stale ReadAllMessages godoc

* Type last_viewed_at_times as int64 map in OpenAPI

The response field was declared as a generic object. Add
additionalProperties so generated clients see it as a
channelId -> int64 timestamp map.

* Gate MarkAllAsReadToast mount on feature flag

The toast was mounted unconditionally, so its async chunk loaded
even when EnableShiftEscapeToMarkAllRead was off. Gate the mount
with the flag so the chunk only loads when the feature is on.

* Return data from markAllInTeamAsRead thunk

Match the {data: response} shape used by adjacent thunks instead
of returning {}, so callers can read the API payload.

* Coerce undefined suffix in createStoredKey

createStoredKey('foo') returned 'fooundefined' when the suffix
arg was omitted. Coerce a missing suffix to ''.

* Refactor mark-read websocket events

* Polish DM/GM channels-with-unreads query

* Fix import order in shortcut_key consumers

* Fix CI

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
2026-05-13 16:38:30 +00:00
Daniel Espino García 55496c07c1 Update API docs (#36302)
* Update API docs

* Coderabbit comments

* Address feedback

* Address feedback

* Coderabbit feedback
2026-05-11 12:29:25 +02:00
Harshil Sharma 56089922e3 Data spillage report generation (#36339)
* Added base fr report generation

* WIP

* Refactoring and cleanup

* lint fixes, added new tests

* test fix

* Several improvements

* Addressed some security enhancements

* Created zip writer entery later

* Improved a test to check for file content

* Improved error handling

* Made a geneeric function

* accepting comment in report API

* Removed an unnecessary check

* Made a geneeric function

* Made the comment body not required and updated API docs
2026-05-08 09:27:13 -04:00
Devin BinnieandMattermost Build 69fbaeced9 [MM-68496] Feature flag Managed Categories, expose Default Category Name to UI for channel creation and settings (#36289)
* [MM-68496] Feature flag Managed Categories, expose Default Category Name to UI for Channels

* PR feedback

* PR feedback

* Fix i18n

* Fix test

* Fix E2E

* Merge'd

* Add tests

* Re-add old tests (skipped)

* Add IncrementVersion to PropertyGroup store, increment version on managed category group

* Fix lint

* Fix mock

* Fix prettier

* Add tests

* Fixed issue when moving from existing category to existing category

* Fix e2e

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-07 09:37:53 -04:00
Guillermo VayáandClaude Opus 4.7 ecf8a741ac Add unread badge to Recaps sidebar link (#36246)
* 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>
2026-05-06 15:14:18 +02:00
1a88e3b322 Adds experimental label to the views endpoints (#36398)
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-06 10:15:49 +02:00
Maria A NunezandMattermost Build 724c5b7191 CPA Display Name Support (#36247)
* Phase 1: CPA display_name + CEL-safe name validation (server)

- Add typed DisplayName field to CPAAttrs + display_name attr key constant.
- Add ValidateCPAFieldName helper enforcing CEL IDENTIFIER + reserved-word blacklist.
- Wire validation into App.CreateCPAField (always) and App.PatchCPAField (lenient grandfather: skip when Name unchanged).
- Trim + 255-rune cap DisplayName in CPAField.SanitizeAndValidate.
- Developer-facing godoc note documenting rule, sources of truth, and Option C scoping.
- Asserting test for documented Option C plugin-API bypass (closed by PR #36173).

Spec: planner/projects/property-display-name/ideas/001-cpa-display-name/spec.md
Plan: .planning/phase-1/PLAN.md
Made-with: Cursor

* Phase 1 (review): address Reza's Major + Minor findings

- Rename misleading subtest "empty DisplayName is omitted from attrs"
  to "empty DisplayName round-trips as empty string" (Major #1).
- Add TestCPAAttrs_JSONOmitEmpty pinning the omitempty wire-format
  contract that PR #36173's typed-attrs strategy relies on (Major #1).
- Extend TestValidateCPAFieldName: case-sensitivity (IN/In ok),
  single-character names (a/_/A ok), missing "as" reserved word
  (Minor #2). Add whitespace-only DisplayName case (Minor #2).
- Document PropertyFieldNameMaxRunes reuse in SanitizeAndValidate
  to prevent drift (Minor #3).
- Replace broken PLAN-server.md reference in bypass-test docstring
  with in-tree CPAAttrs godoc reference (Minor #4).
- Document omitempty semantics on CPAAttrs.DisplayName field to
  prevent the same misreading caught in review (Minor #5).
- Document grouping intent above CPAFieldNameReservedWords (Minor #8).

Review: .planning/phase-1/REVIEW.md
Made-with: Cursor

* Phase 2: in-app backfill migration for CPA display_name

- Add cpaDisplayNameBackfillKey + cpaDisplayNameBackfillVersion constants.
- Implement (*Server).doSetupCPADisplayNameBackfill: idempotent, cursor-paged
  scan over CPA group fields; backfill attrs.display_name = name when empty.
- Register in m1 migration slice in doAppMigrations (mlog.Fatal on error,
  matching existing convention).
- Three migration tests: NoExistingFields, BackfillsMissing, Idempotent.

System-key idempotency + per-field DisplayName-empty check together provide
HA-safe behavior on rolling deploys (last-write-wins on the System key;
data-level idempotency from the per-field check).

Spec: planner/projects/property-display-name/ideas/001-cpa-display-name/spec.md
Plan: .planning/phase-2/PLAN.md
Made-with: Cursor

* Phase 2 (review): document race + harden idempotency test

- Document SearchPropertyFields→UpdatePropertyFields rolling-deploy
  race: stale snapshot can revert concurrent admin CPA rename. Pre-
  existing systemic shape (no UpdateAt optimistic-lock); narrow
  window; bounded blast radius (admin re-rename, ABAC ID-keyed).
  Accepted limitation per spec Out of Scope (Major #1, Option C).
- Tighten TestCPADisplayNameBackfill_Idempotent: snapshot UpdateAt
  before second run; assert no DB write on the System key or the
  field row (Major #2).
- Extract clearCPABackfillMarker helper with explanatory godoc to
  centralize the 3x-repeated test precondition (Minor #1).
- Comment fieldA seed as the "key-present-as-empty-string" idempotency
  boundary case (Minor #6).
- Add godoc to doSetupCPADisplayNameBackfill (Minor #10).

Review: .planning/phase-2/REVIEW.md
Made-with: Cursor

* Linting

* Removing unnecessary comments

* Clean up tests

* Linting

* Fix tests

* Updated API doc

* Fix tests

* PR Feedback

* Move migration to PropertyService

* Linting

* Linting

* Removed pagination

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-04 10:33:05 -04:00
Ibrahim Serdar Acikgoz 4da11e81af [MM-68497] Enables membership policies on public channels with advisory semantics (#36275) 2026-04-30 00:56:32 +02:00
David Krauser 6c0e0fee4a [MM-68464] Introduce system object type for property fields and values (#36250) 2026-04-29 18:47:34 +00:00
c2ec9e967d Add stronger EnableTesting warnings (#36158)
* Add stronger EnableTesting warnings

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Keep EnableTesting translations in en only

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Address EnableTesting review feedback

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-04-29 12:17:23 -04:00
David Krauser 3fa8776095 [MM-68100] Implement Linked Properties for the Property System (#35808) 2026-04-21 18:59:12 +00:00
Doug Lauder 9fa8c8c0c8 Add bulk set (replace) channel memberships API endpoint (#36031)
* Add bulk set (replace) channel memberships API

  PUT /api/v4/channels/{channel_id}/members accepts a complete desired
  membership list and reconciles it against the current state, adding
  missing users and removing extras while leaving existing members
  untouched. Results stream back as NDJSON with configurable batch size
  and delay to manage server load. Sysadmin only. Private channels
  cannot be emptied entirely.
2026-04-15 01:41:33 -04:00
Devin Binniecoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>Matthew BirtchHarrison HealeyMattermost Build
01219efbf4 [MM-68037] Managed Sidebar Categories (MVF) (#35935)
* [MM-68037] Managed Sidebar Categories (MVF)

* PR feedback

* PR feedback

* Fix test issue again

* Fixed a few things

* Fix again

* PR feedback

* Update server/i18n/en.json

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

* Update server/i18n/en.json

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

* Update webapp/channels/src/packages/mattermost-redux/src/actions/channel_categories.ts

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

* PR feedback

* PR feedback

* More PR feedback

* Test fixes

* This one too

* PR feedback

* more

* More feedback

* More

* more

* Yup

* More

* PR feedback

* Update webapp/channels/src/components/channel_settings_modal/managed_category_selector.scss

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

* Block setting behind Enterprise license

* Update webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.ts

Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>

* Update webapp/channels/src/packages/mattermost-redux/src/actions/channel_categories.ts

Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>

* PR feedback

* Don't await for the initial managed category check

* Turn into its own action

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
2026-04-14 09:00:59 -04:00
Doug Lauder 050e41f7b7 Doc line for new websocket event (#35939) 2026-04-03 14:50:44 -04:00
c81d0ddd73 Ability to E2E AI Bridge features + Initial Recaps E2E (#35541)
* Add shared AI bridge seam

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add AI bridge test helper API

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add AI bridge seam test coverage

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Add Playwright AI bridge recap helpers

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix recap channel persistence test

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Restore bridge client compatibility shim

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Expand recap card in Playwright spec

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Recaps e2e test coverage (#35543)

* Add Recaps Playwright page object

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Expand AI recap Playwright coverage

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Format recap Playwright coverage

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix recap regeneration test flows

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

---------

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

* Fix AI bridge lint and OpenAPI docs

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix recap lint shadowing

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Stabilize failed recap regeneration spec

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fill AI bridge i18n strings

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Fix i18n

* Add service completion bridge path and operation tracking fields

Extend AgentsBridge with CompleteService for service-based completions,
add ClientOperation/OperationSubType tracking to BridgeCompletionRequest,
and propagate operation metadata through to the bridge client.

Made-with: Cursor

* Fill empty i18n translation strings for enterprise keys

The previous "Fix i18n" commit added 145 i18n entries with empty
translation strings, causing the i18n check to fail in CI. Fill in
all translations based on the corresponding error messages in the
enterprise and server source code.

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

* Fix i18n

* Fix i18n again

* Rename Complete/CompleteService to AgentCompletion/ServiceCompletion

Align the AgentsBridge interface method names with the underlying
bridge client methods they delegate to (AgentCompletion, ServiceCompletion).

Made-with: Cursor

* Refactor

* Add e2eAgentsBridge implementation

The new file was missed from the prior refactor commit.

Made-with: Cursor

* Address CodeRabbit review feedback

- Add 400 BadRequest response to AI bridge PUT endpoint OpenAPI spec
- Add missing client_operation, operation_sub_type, service_id fields to
  AIBridgeTestHelperRecordedRequest schema
- Deep-clone nested JSON schema values in cloneJSONOutputFormat
- Populate ChannelID on recap summary bridge requests
- Fix msg_count assertion to mention_count for mark-as-read verification
- Make AgentCompletion/ServiceCompletion mutex usage atomic

Made-with: Cursor

* fix(playwright): align recaps page object with placeholder and channel menu

Made-with: Cursor

* fix(playwright): update recaps expectEmptyState to match RecapsList empty state

After the master merge, the recaps page now renders RecapsList's
"You're all caught up" empty state instead of the old placeholder.

Made-with: Cursor

* chore(playwright): update package-lock.json after npm install

Made-with: Cursor

* Revert "chore(playwright): update package-lock.json after npm install"

This reverts commit 95c670863a.

* style(playwright): fix prettier formatting in recaps page object

Made-with: Cursor

* fix(playwright): handle both recaps empty states correctly

The recaps page has two distinct empty states:
- Setup placeholder ("Set up your recap") when allRecaps is empty
- RecapsList caught-up state ("You're all caught up") when the
  filtered tab list is empty

Split expectEmptyState into expectSetupPlaceholder and
expectCaughtUpEmptyState, used by the delete and bridge-unavailable
tests respectively.

Made-with: Cursor

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:20:47 +00:00
48f2fd0873 Merge the Integrated Boards MVP feature branch (#35796)
* Add CreatedBy and UpdatedBy to the properties fields and values (#34485)

* Add CreatedBy and UpdatedBy to the properties fields and values

* Fix types

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Adds ObjectType to the property fields table (#34908)

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Update ObjectType migration setting an empty value and marking the column as not null (#34915)

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Adds uniqueness mechanisms to the property fields (#35058)

* Adds uniqueness mechanisms to the property fields

After adding ObjectType, this commit ensures that both the PSAv1 and
PSAv2 schemas are supported, and enforces property uniqueness through
both database indexes and a logical check when creating new property
fields.

* Adds uniqueness check to property updates

Updates are covered on this commit and we refactor as well the SQL
code to use the squirrel builder and work better with the conditional
addition of the `existingID` piece of the query.

* Add translations to error messages

* Fixing retrylayer mocks

* Remove retrylayer duplication

* Address review comments

* Fix comment to avoid linter issues

* Address PR comments

* Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.down.sql

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.up.sql

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Update server/channels/db/migrations/postgres/000157_add_object_type_to_property_fields.up.sql

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Update field validation to check only for valid target types

* Update migrations to avoid concurrent index creation within a transaction

* Update migrations to make all index ops concurrent

* Update tests to use valid PSAv2 property fields

* Adds a helper for valid PSAv2 TargetTypes

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Fix property tests (#35388)

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Adds Integrated Boards feature flag (#35378)

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Adds Integrated Boards MVP API changes (#34822)

This PR includes the necessary changes for channels and posts
endpoints and adds a set of generic endpoints to retrieve and manage
property fields and values following the new Property System approach.

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Mattermost Build <build@mattermost.com>

* Property System Architecture permissions for v2 (#35113)

* Adds uniqueness mechanisms to the property fields

After adding ObjectType, this commit ensures that both the PSAv1 and
PSAv2 schemas are supported, and enforces property uniqueness through
both database indexes and a logical check when creating new property
fields.

* Adds uniqueness check to property updates

Updates are covered on this commit and we refactor as well the SQL
code to use the squirrel builder and work better with the conditional
addition of the `existingID` piece of the query.

* Add translations to error messages

* Add the permissions to the migrations, model and update the store calls

* Adds the property field and property group app layer

* Adds authorization helpers for property fields and values

* Make sure that users cannot lock themselves out of property fields

* Migrate permissions from a JSON column to three normalized columns

* Remove the audit comment

* Use target level constants in authorization

* Log authorization membership failures

* Rename admin to sysadmin

* Fix i18n sorting

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Add Views store and app layer (#35361)

* Add Views store and app layer for Integrated Boards

Implements the View entity (model, SQL store, service, app) as described
in the Integrated Boards tech spec. Views are channel-scoped board
configurations with typed props (board, kanban subviews) and soft-delete.

- public/model: View, ViewBoardProps, Subview, ViewPatch types with
  PreSave/PreUpdate/IsValid/Patch/Clone/Auditable
- Migration 158: Views table with jsonb Props column and indexes
- SqlViewStore: CRUD with nil-safe Props marshaling (AppendBinaryFlag)
- ViewService: CreateView seeds default kanban subview and links the
  boards property field; caches boardPropertyFieldID at startup
- App layer: CreateView/GetView/GetViewsForChannel/UpdateView/DeleteView
  with channel-membership permission checks and WebSocket events
  (view_created, view_updated, view_deleted)
- doSetupBoardsPropertyField: registers the Boards property group and
  board field in NewServer() before ViewService construction
- GetFieldByName now returns store.ErrNotFound instead of raw sql.ErrNoRows

* Move permission checks out of App layer for views

- Remove HasPermissionToChannel calls from all App view methods
- Drop userID params from GetView, GetViewsForChannel, UpdateView, DeleteView
- Fix doSetupBoardsPropertyField to include required TargetType for PSAv2 field

* Make View service generic and enforce board validation in model

- Remove board-specific auto-setup from service and server startup
- Enforce that board views require Props, at least one subview, and at least one linked property in IsValid()
- Move default subview seeding out of app layer; callers must provide valid props
- Call PreSave on subviews during PreUpdate to assign IDs to new subviews
- Update all tests to reflect the new validation requirements

* Restore migrations files to match base branch

* Distinguish ErrNotFound from other errors in view store Get

* Use CONCURRENTLY and nontransactional for index operations in views migration

* Split views index creation into separate nontransactional migrations

* Update migrations.list

* Update i18n translations for views

* Fix makeView helper to include required Props for board view validation

* Rename ctx parameter from c to rctx in OAuthProvider mock

* Remove views service layer, call store directly from app

* Return 500 for unexpected DB errors in GetView, 404 only for not-found

* Harden View model: deep-copy Props, validate linked property IDs

- Add ViewBoardProps.Clone() to deep-copy LinkedProperties and Subviews
- Use it in View.Clone() and View.Patch() to prevent shared-slice aliasing
- Iterate over LinkedProperties in View.IsValid() and reject invalid IDs
  with a dedicated i18n key
- Register ViewStore in storetest AssertExpectations so mock expectations
  are enforced
- Add tests covering all new behaviours

* Restore autotranslation worker_stopped i18n translation

* Fix view store test IDs and improve error handling in app layer

- Use model.NewId() for linked property IDs in testUpdateView to fix
  validation failure (IsValid rejects non-UUID strings)
- Fix import grouping in app/view.go (stdlib imports in one block)
- Return 404 instead of 500 when Update/Delete store calls return
  ErrNotFound (e.g. concurrent deletion TOCTOU race)

* Add View store mock to retrylayer test genStore helper

The View store was added to the store interface but the genStore()
helper in retrylayer_test.go was not updated, causing TestRetry to panic.
Also removes the duplicate Recap mock registration.

* Refactor view deletion and websocket event handling; update SQL store methods to use query builder

* revert property field store

* Remove useless migrations

* Add cursor-based pagination to View store GetForChannel

- Add ViewQueryCursor and ViewQueryOpts types with validation
- Return (views, cursor, error) for caller-driven pagination
- PerPage clamping: <=0 defaults to 20, >200 clamps to 200
- Support IncludeDeleted filter
- Add comprehensive store tests for pagination, cursor edge cases,
  PerPage clamping, and invalid input rejection
- Add app layer test for empty channelID → 400
- Update interface, retrylayer, timerlayer, and mock signatures

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

* Refactor test loops in ViewStore tests for improved readability

* change pagination to limit/offset

* Add upper-bound limits on View Subviews and LinkedProperties

Defense-in-depth validation: cap Subviews at 50 and LinkedProperties
at 500 to prevent abuse below the 300KB payload limit.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* MM-67388, MM-66528, MM-67750: Add View REST API endpoints, websocket events, and sort order (#35442)

* Add Views store and app layer for Integrated Boards

Implements the View entity (model, SQL store, service, app) as described
in the Integrated Boards tech spec. Views are channel-scoped board
configurations with typed props (board, kanban subviews) and soft-delete.

- public/model: View, ViewBoardProps, Subview, ViewPatch types with
  PreSave/PreUpdate/IsValid/Patch/Clone/Auditable
- Migration 158: Views table with jsonb Props column and indexes
- SqlViewStore: CRUD with nil-safe Props marshaling (AppendBinaryFlag)
- ViewService: CreateView seeds default kanban subview and links the
  boards property field; caches boardPropertyFieldID at startup
- App layer: CreateView/GetView/GetViewsForChannel/UpdateView/DeleteView
  with channel-membership permission checks and WebSocket events
  (view_created, view_updated, view_deleted)
- doSetupBoardsPropertyField: registers the Boards property group and
  board field in NewServer() before ViewService construction
- GetFieldByName now returns store.ErrNotFound instead of raw sql.ErrNoRows

* Move permission checks out of App layer for views

- Remove HasPermissionToChannel calls from all App view methods
- Drop userID params from GetView, GetViewsForChannel, UpdateView, DeleteView
- Fix doSetupBoardsPropertyField to include required TargetType for PSAv2 field

* Make View service generic and enforce board validation in model

- Remove board-specific auto-setup from service and server startup
- Enforce that board views require Props, at least one subview, and at least one linked property in IsValid()
- Move default subview seeding out of app layer; callers must provide valid props
- Call PreSave on subviews during PreUpdate to assign IDs to new subviews
- Update all tests to reflect the new validation requirements

* Restore migrations files to match base branch

* Distinguish ErrNotFound from other errors in view store Get

* Use CONCURRENTLY and nontransactional for index operations in views migration

* Split views index creation into separate nontransactional migrations

* Update migrations.list

* Update i18n translations for views

* Fix makeView helper to include required Props for board view validation

* Rename ctx parameter from c to rctx in OAuthProvider mock

* Remove views service layer, call store directly from app

* Return 500 for unexpected DB errors in GetView, 404 only for not-found

* Harden View model: deep-copy Props, validate linked property IDs

- Add ViewBoardProps.Clone() to deep-copy LinkedProperties and Subviews
- Use it in View.Clone() and View.Patch() to prevent shared-slice aliasing
- Iterate over LinkedProperties in View.IsValid() and reject invalid IDs
  with a dedicated i18n key
- Register ViewStore in storetest AssertExpectations so mock expectations
  are enforced
- Add tests covering all new behaviours

* Restore autotranslation worker_stopped i18n translation

* Fix view store test IDs and improve error handling in app layer

- Use model.NewId() for linked property IDs in testUpdateView to fix
  validation failure (IsValid rejects non-UUID strings)
- Fix import grouping in app/view.go (stdlib imports in one block)
- Return 404 instead of 500 when Update/Delete store calls return
  ErrNotFound (e.g. concurrent deletion TOCTOU race)

* Add View store mock to retrylayer test genStore helper

The View store was added to the store interface but the genStore()
helper in retrylayer_test.go was not updated, causing TestRetry to panic.
Also removes the duplicate Recap mock registration.

* Refactor view deletion and websocket event handling; update SQL store methods to use query builder

* revert property field store

* Add View API endpoints with OpenAPI spec, client methods, and i18n

Implement REST API for channel views (board-type) behind the
IntegratedBoards feature flag. Adds CRUD endpoints under
/api/v4/channels/{channel_id}/views with permission checks
matching the channel bookmark pattern.

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

* Remove useless migrations

* Add cursor-based pagination to View store GetForChannel

- Add ViewQueryCursor and ViewQueryOpts types with validation
- Return (views, cursor, error) for caller-driven pagination
- PerPage clamping: <=0 defaults to 20, >200 clamps to 200
- Support IncludeDeleted filter
- Add comprehensive store tests for pagination, cursor edge cases,
  PerPage clamping, and invalid input rejection
- Add app layer test for empty channelID → 400
- Update interface, retrylayer, timerlayer, and mock signatures

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

* Add cursor-based pagination to View API for channel views

* Enhance cursor handling in getViewsForChannel and update tests for pagination

* Refactor test loops in ViewStore tests for improved readability

* Refactor loop in TestGetViewsForChannel for improved readability

* change pagination to limit/offset

* switch to limit/offset pagination

* Add upper-bound limits on View Subviews and LinkedProperties

Defense-in-depth validation: cap Subviews at 50 and LinkedProperties
at 500 to prevent abuse below the 300KB payload limit.

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

* Add view sort order API endpoint

Add POST /api/v4/channels/{channel_id}/views/{view_id}/sort_order
endpoint following the channel bookmarks reorder pattern. Includes
store, app, and API layers with full test coverage at each layer.

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

* Add connectionId to view WebSocket events and sort_order API spec

Thread connectionId from request header through all view handlers
(create, update, delete, sort_order) to WebSocket events, matching
the channel bookmarks pattern. Add sort_order endpoint to OpenAPI
spec. Update minimum server version to 11.6.

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

* Remove duplicate View/ViewPatch definitions from definitions.yaml

The merge from integrated-boards-mvp introduced duplicate View and
ViewPatch schema definitions that were already defined earlier in
the file with more detail (including ViewBoardProps ref and enums).

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

* Update minimum server version to 11.6 in views API spec

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

* Add missing translations for view sort order error messages

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

* Merge integrated-boards-mvp into ibmvp_api-views; remove spec files

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

* Fix flaky TestViewStore timestamp test on CI

Add sleep before UpdateSortOrder to ensure timestamps differ,
preventing same-millisecond comparisons on fast CI machines.

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

* remove duplicate views.yaml imclude

* Use c.boolString() for include_deleted query param in GetViewsForChannel

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

* Fix views.yaml sort order schema: use integer type and require body

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

* Refactor view sort order tests to use named IDs instead of array indices

Extract idA/idB/idC from views slice and add BEFORE/AFTER comments
to make stateful subtest ordering easier to follow.

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

* Return 404 instead of 403 for view operations on deleted channels

Deleted channels should appear non-existent to callers rather than
revealing their existence via a 403. Detailed error text explains
the context for debugging.

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

* add missing channel deleteat checks

* Use c.Params.Page instead of manual page query param parsing in getViewsForChannel

c.Params already validates and defaults page/per_page, so the manual
parsing was redundant.

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

* Add support for total count in views retrieval

* Add tests for handling deleted views in GetViewsForChannel and GetView

* Short-circuit negative newIndex in UpdateSortOrder before opening transaction

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

* Add per-channel limit on views to bound UpdateSortOrder cost

Without a cap, unbounded view creation makes sort-order updates
increasingly expensive (CASE WHEN per view, row locks). Adds
MaxViewsPerChannel=50 constant and enforces it in the app layer
before saving. Includes API and app layer tests.

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

* Remove include_deleted support from views API

Soft-deleted views are structural metadata with low risk, but no other
similar endpoint (e.g. channel bookmarks) exposes deleted records without
an admin gate. Rather than adding an admin-only permission check for
consistency, remove the feature entirely since there is no current use case.

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

* Update view permissions to require `create_post` instead of channel management permissions

* Remove obsolete view management error messages for direct and group messages

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(migrations): add user tracking and object type to property fields

- Introduced user tracking columns (CreatedBy, UpdatedBy) to PropertyFields and PropertyValues.
- Added ObjectType column to PropertyFields with associated unique indexes for legacy and typed properties.
- Created new migration scripts for adding and dropping these features, including necessary indexes for data integrity.
- Established views for managing property fields with new attributes.

This update enhances the schema to support better tracking and categorization of property fields.

* Add Property System Architecture v2 API endpoints (#35583)

* Adds uniqueness mechanisms to the property fields

After adding ObjectType, this commit ensures that both the PSAv1 and
PSAv2 schemas are supported, and enforces property uniqueness through
both database indexes and a logical check when creating new property
fields.

* Adds uniqueness check to property updates

Updates are covered on this commit and we refactor as well the SQL
code to use the squirrel builder and work better with the conditional
addition of the `existingID` piece of the query.

* Add translations to error messages

* Add the permissions to the migrations, model and update the store calls

* Adds the property field and property group app layer

* Adds authorization helpers for property fields and values

* Make sure that users cannot lock themselves out of property fields

* Migrate permissions from a JSON column to three normalized columns

* Remove the audit comment

* Use target level constants in authorization

* Log authorization membership failures

* Rename admin to sysadmin

* Adds the Property System Architecture v2 API endpoints

* Adds permission checks to the create field endpoint

* Add target access checks to value endpoints

* Add default branches for object_type and target_type and extra guards for cursor client4 methods

* Fix vet API mismatch

* Fix error checks

* Fix linter

* Add merge semantics for property patch logic and API endpoint

* Fix i18n

* Fix duplicated patch elements and early return on bad cursor

* Update docs to use enums

* Fix i18n sorting

* Update app layer to return model.AppError

* Adds a limit to the number of property values that can be patched in the same request

* Require target_type filter when searching property fields

* Add objectType validation as part of field.IsValid()

* Fix linter

* Fix test with bad objecttpye

* Fix test grouping

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* MM-67968: Flatten view model — remove icon, subviews, typed board props (#35726)

* feat(views): flatten view model by removing icon, subview, and board props

Simplifies the View data model as part of MM-67968: removes Icon, Subview,
and ViewBoardProps types; renames ViewTypeBoard to ViewTypeKanban; replaces
typed Props with StringInterface (map[string]any); adds migration 000167
to drop the Icon column from the Views table.

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

* feat(api): update views OpenAPI spec to reflect flattened model

Removes ViewBoardProps, Subview, and icon from the View and ViewPatch
schemas. Changes type enum from board to kanban. Replaces typed props
with a free-form StringInterface object. Aligns with MM-67968.

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

* refactor(views): simplify store by dropping dbView and marshalViewProps

StringInterface already implements driver.Valuer and sql.Scanner, so the
manual JSON marshal/unmarshal and the dbView intermediate struct were
redundant. model.View now scans directly from the database. Also removes
the dead ViewMaxLinkedProperties constant and wraps the Commit() error in
UpdateSortOrder.

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

* fix(api): allow arbitrary JSON in view props OpenAPI schema

The props field was restricted to string values via
additionalProperties: { type: string }, conflicting with the Go model's
StringInterface (map[string]any). Changed to additionalProperties: true
in View, ViewPatch, and inline POST schemas.

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

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* Adds basic implementation of the generic redux store for PSAv2 (#35512)

* Adds basic implementation of the generic redux store for PSAv2

* Add created_by and updated_by to the test fixtures

* Make target_id, target_type and object_type mandatory

* Wrap getPropertyFieldsByIds and getPropertyValuesForTargetByFieldIds with createSelector

* Address PR comments

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* Adds websocket messages for the PSAv2 API events (#35696)

* Adds uniqueness mechanisms to the property fields

After adding ObjectType, this commit ensures that both the PSAv1 and
PSAv2 schemas are supported, and enforces property uniqueness through
both database indexes and a logical check when creating new property
fields.

* Adds uniqueness check to property updates

Updates are covered on this commit and we refactor as well the SQL
code to use the squirrel builder and work better with the conditional
addition of the `existingID` piece of the query.

* Add translations to error messages

* Add the permissions to the migrations, model and update the store calls

* Adds the property field and property group app layer

* Adds authorization helpers for property fields and values

* Make sure that users cannot lock themselves out of property fields

* Migrate permissions from a JSON column to three normalized columns

* Remove the audit comment

* Use target level constants in authorization

* Log authorization membership failures

* Rename admin to sysadmin

* Adds the Property System Architecture v2 API endpoints

* Adds permission checks to the create field endpoint

* Add target access checks to value endpoints

* Add default branches for object_type and target_type and extra guards for cursor client4 methods

* Fix vet API mismatch

* Fix error checks

* Fix linter

* Add merge semantics for property patch logic and API endpoint

* Fix i18n

* Fix duplicated patch elements and early return on bad cursor

* Update docs to use enums

* Fix i18n sorting

* Update app layer to return model.AppError

* Adds a limit to the number of property values that can be patched in the same request

* Adds websocket messages for the PSAv2 API events

* Add IsPSAv2 helper to the property field for clarity

* Add guard against nil returns on field deletion

* Add docs to the websocket endpoints

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>

* migrations: consolidate views migrations and reorder after master

- Merged 000165 (create Views) with 000167 (drop Icon) since Icon was never needed
- Renumbered branch migrations 159-166 → 160-167 so master's 000159 (deduplicate_policy_names) runs first
- Regenerated migrations.list

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

* Add API endpoint to retrieve posts for a specific view (#35604)

Automatic Merge

* Apply fixes after merge

* Return a more specific error from getting multiple fields

* Prevent getting broadcast params on field deletion if not needed

* Remove duplicated migration code

* Update property conflict code to always use master

* Adds nil guard when iterating on property fields

* Check that permission level is valid before getting rejected by the database

* Validate correctness on TargetID for PSAv2 fields

* Avoid PSAv1 using permissions or protected

* Fix test data after validation change

* Fix flaky search test

* Adds more posts for filter use cases to properly test exclusions

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
Co-authored-by: Julien Tant <julien@craftyx.fr>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Julien Tant <785518+JulienTant@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 10:36:35 +01:00
Doug Lauder 162ed1bacd MM-67684 Separate shared channel permissions from secure connection permissions (#35409)
* Channel sharing operations (invite, uninvite, list shared channel remotes)
now require ManageSharedChannels instead of ManageSecureConnections, allowing
customers to delegate channel sharing without granting full connection management access.
Endpoints serving both roles (getRemoteClusters, getSharedChannelRemotesByRemoteCluster) accept either permission.

Also adds RequirePermission helpers on Context to reduce boilerplate across all remote cluster and shared channel handlers, and fixes a bug where invite/uninvite checked ManageSecureConnections but reported ManageSharedChannels in the error.
2026-03-11 15:53:06 -04:00
b6e5264731 [MM-67739] Rename SlackAttachment to MessageAttachment across the codebase (#35445)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-10 16:37:21 +01:00
Renato AlvesandMattermost Build cd85c7e283 Consistent use of uppercase LDAP tag (#31292)
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-05 19:16:33 +00:00
Renato AlvesandMattermost Build b9bf16135f Add operationId to content_flagging endpoints (#34231)
Needed for https://github.com/embl-bio-it/python-mattermost-autodriver

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-05 19:15:00 +00:00
062abe90bd Includes deleted remote cluster infos to correctly show shared user information (#35192)
* Includes deleted remote cluster infos to correctly show shared user information

* Addressing review comments

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-04 14:46:10 +01:00
Pablo VélezandMattermost Build 37a9a30f40 Mm 66813 sso callback metadata (#34955)
* MM-66813 - Add server origin verification to mobile SSO callbacks

* Enhance mobile SSO security and deprecate code-exchange

* Update code-exchange deprecation to follow MM standards

* Use config SiteURL for srv param, fix flow terminology

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-02-16 11:07:02 -05:00
Miguel de la CruzandMiguel de la Cruz 51426954cf Removes the experimental label from CPA endpoints (#35180)
Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
2026-02-13 10:37:00 +00:00