Files
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

99 lines
3.7 KiB
Makefile

ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
.PHONY: build build-v4 clean playbooks
V4_YAML = $(ROOT)/v4/html/static/mattermost-openapi-v4.yaml
V4_SRC = $(ROOT)/v4/source
PLAYBOOKS_SRC = $(ROOT)/playbooks
build: build-v4
build-v4: node_modules playbooks
@echo Building mattermost openapi yaml for v4
@if [ -r $(PLAYBOOKS_SRC)/merged-tags.yaml ]; then cat $(PLAYBOOKS_SRC)/merged-tags.yaml > $(V4_YAML); else cat $(V4_SRC)/introduction.yaml > $(V4_YAML); fi
@cat $(V4_SRC)/users.yaml >> $(V4_YAML)
@cat $(V4_SRC)/status.yaml >> $(V4_YAML)
@cat $(V4_SRC)/teams.yaml >> $(V4_YAML)
@cat $(V4_SRC)/channels.yaml >> $(V4_YAML)
@cat $(V4_SRC)/posts.yaml >> $(V4_YAML)
@cat $(V4_SRC)/preferences.yaml >> $(V4_YAML)
@cat $(V4_SRC)/files.yaml >> $(V4_YAML)
@cat $(V4_SRC)/recaps.yaml >> $(V4_YAML)
@cat $(V4_SRC)/uploads.yaml >> $(V4_YAML)
@cat $(V4_SRC)/jobs.yaml >> $(V4_YAML)
@cat $(V4_SRC)/system.yaml >> $(V4_YAML)
@cat $(V4_SRC)/emoji.yaml >> $(V4_YAML)
@cat $(V4_SRC)/webhooks.yaml >> $(V4_YAML)
@cat $(V4_SRC)/saml.yaml >> $(V4_YAML)
@cat $(V4_SRC)/compliance.yaml >> $(V4_YAML)
@cat $(V4_SRC)/ldap.yaml >> $(V4_YAML)
@cat $(V4_SRC)/groups.yaml >> $(V4_YAML)
@cat $(V4_SRC)/cluster.yaml >> $(V4_YAML)
@cat $(V4_SRC)/brand.yaml >> $(V4_YAML)
@cat $(V4_SRC)/commands.yaml >> $(V4_YAML)
@cat $(V4_SRC)/oauth.yaml >> $(V4_YAML)
@cat $(V4_SRC)/elasticsearch.yaml >> $(V4_YAML)
@cat $(V4_SRC)/dataretention.yaml >> $(V4_YAML)
@cat $(V4_SRC)/plugins.yaml >> $(V4_YAML)
@cat $(V4_SRC)/roles.yaml >> $(V4_YAML)
@cat $(V4_SRC)/schemes.yaml >> $(V4_YAML)
@cat $(V4_SRC)/service_terms.yaml >> $(V4_YAML)
@cat $(V4_SRC)/remoteclusters.yaml >> $(V4_YAML)
@cat $(V4_SRC)/sharedchannels.yaml >> $(V4_YAML)
@cat $(V4_SRC)/reactions.yaml >> $(V4_YAML)
@cat $(V4_SRC)/actions.yaml >> $(V4_YAML)
@cat $(V4_SRC)/bots.yaml >> $(V4_YAML)
@cat $(V4_SRC)/cloud.yaml >> $(V4_YAML)
@cat $(V4_SRC)/usage.yaml >> $(V4_YAML)
@cat $(V4_SRC)/permissions.yaml >> $(V4_YAML)
@cat $(V4_SRC)/imports.yaml >> $(V4_YAML)
@cat $(V4_SRC)/exports.yaml >> $(V4_YAML)
@cat $(V4_SRC)/ip_filters.yaml >> $(V4_YAML)
@cat $(V4_SRC)/bookmarks.yaml >> $(V4_YAML)
@cat $(V4_SRC)/boards.yaml >> $(V4_YAML)
@cat $(V4_SRC)/views.yaml >> $(V4_YAML)
@cat $(V4_SRC)/reports.yaml >> $(V4_YAML)
@cat $(V4_SRC)/limits.yaml >> $(V4_YAML)
@cat $(V4_SRC)/logs.yaml >> $(V4_YAML)
@cat $(V4_SRC)/outgoing_oauth_connections.yaml >> $(V4_YAML)
@cat $(V4_SRC)/metrics.yaml >> $(V4_YAML)
@cat $(V4_SRC)/scheduled_post.yaml >> $(V4_YAML)
@cat $(V4_SRC)/custom_profile_attributes.yaml >> $(V4_YAML)
@cat $(V4_SRC)/audit_logging.yaml >> $(V4_YAML)
@cat $(V4_SRC)/access_control.yaml >> $(V4_YAML)
@cat $(V4_SRC)/content_flagging.yaml >> $(V4_YAML)
@cat $(V4_SRC)/agents.yaml >> $(V4_YAML)
@cat $(V4_SRC)/scheduled_recaps.yaml >> $(V4_YAML)
@cat $(V4_SRC)/properties.yaml >> $(V4_YAML)
@if [ -r $(PLAYBOOKS_SRC)/paths.yaml ]; then cat $(PLAYBOOKS_SRC)/paths.yaml >> $(V4_YAML); fi
@if [ -r $(PLAYBOOKS_SRC)/merged-definitions.yaml ]; then cat $(PLAYBOOKS_SRC)/merged-definitions.yaml >> $(V4_YAML); else cat $(V4_SRC)/definitions.yaml >> $(V4_YAML); fi
@echo Extracting code samples
cd server && go run . $(V4_YAML)
@node_modules/.bin/swagger-cli validate $(V4_YAML)
@cp ./v4/html/ssr_template.hbs ./v4/html/index.html
@echo Complete
node_modules: package.json $(wildcard package-lock.json)
@echo Getting dependencies using npm
npm install
touch $@
run:
@echo Starting local server
python3 -m http.server 8080 --directory ./v4/html
clean:
@echo Cleaning
rm -rf node_modules
playbooks:
@echo Fetching Playbooks OpenAPI spec
cd playbooks && node extract.js
cd playbooks && node merge-definitions.js $(V4_SRC)/definitions.yaml
cd playbooks && node merge-tags.js $(V4_SRC)/introduction.yaml