Skip to content

Add blocks to interactive dialogs - #10024

Open
larkox wants to merge 4 commits into
mainfrom
dialogBlocks
Open

Add blocks to interactive dialogs#10024
larkox wants to merge 4 commits into
mainfrom
dialogBlocks

Conversation

@larkox

@larkox larkox commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds Blocks to render Interactive Dialogs. It covers all the types supported by dialogs as of today (including file elements, which were not supported on mobile yet) and also gives support for other blocks like containers, scrollable, collapsible, etc...

Input blocks can also be used in interactive messages.

We also have modernized the inputs to align more with the current state of the app.

Related PR

WEB/SERVER: mattermost/mattermost#37767

Ticket Link

TBD

Screenshots

Captura de pantalla 2026-08-07 a las 14 02 42 Captura de pantalla 2026-08-07 a las 14 03 27 Captura de pantalla 2026-08-07 a las 14 03 36 Captura de pantalla 2026-08-07 a las 14 03 44 Captura de pantalla 2026-08-07 a las 14 04 14 image

Release Note

- Modernized look and feel of interactive dialogs
- Added Input Blocks to be used in interactive posts
- Allow interactive dialogs mades of Blocks

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Documentation Impact Analysis — updates needed

Documentation Impact Analysis

Overall Assessment: Documentation Updates Recommended

Changes Summary

This PR adds "Blocks" support to Interactive Dialogs on mobile (covering all block-based element types including file input, which was previously unsupported on mobile), adds Input Blocks to interactive messages/posts, and modernizes the look and feel of existing interactive dialog forms. A minimum server version of 11.11.0 is required for the new block action APIs (BLOCK_ACTIONS_VERSION = [11, 11, 0]).

Documentation Impact Details

Change Type Files Changed Affected Personas Documentation Action Docs Location
New or changed API endpoint consumed / New user-facing behavior app/components/block_renderer/file_input_element.tsx (NEW), app/screens/dialog_router/blocks_dialog_shell.tsx (NEW), app/screens/dialog_router/blocks_dialog_router.tsx (NEW), app/components/block_renderer/translation/mm_block.ts Developer / Integrator, End User Document that Interactive Dialogs on mobile now support a blocks-based format and file input elements (previously unavailable on mobile); note server version 11.11.0+ required docs/source/integrations-guide/incoming-webhooks.rst
New or changed UI component (new user interaction) app/components/post_list/post/body/content/interactive_messages/index.tsx (MODIFIED), app/components/block_renderer/text_input_element.tsx (NEW), app/components/block_renderer/select_input_element.tsx (NEW), app/components/block_renderer/bool_input_element.tsx (NEW), app/components/block_renderer/date_input_element/ (NEW), app/components/block_renderer/datetime_input_element/ (NEW) Developer / Integrator Document that Interactive Messages (post attachments) can now include Input Block elements (text, select, bool, date, datetime) on mobile docs/source/integrations-guide/incoming-webhooks.rst
New or changed app configuration or feature flag app/constants/versions.ts (+BLOCK_ACTIONS_VERSION = [11, 11, 0]) System Administrator, Developer / Integrator Note minimum server version 11.11.0 required for block actions in interactive dialogs and input blocks in interactive messages on mobile docs/source/administration-guide/upgrade/important-upgrade-notes.rst or docs/source/end-user-guide/access/client-availability.rst

Recommended Actions

  • Update docs/source/integrations-guide/incoming-webhooks.rst to note that Interactive Dialogs on mobile now support blocks-based dialog format (in addition to the legacy element format), and that Input Block elements (text input, select, boolean, date/datetime, file) are now available in interactive messages on mobile — including file upload which was previously unsupported on mobile.
  • Update docs/source/end-user-guide/access/client-availability.rst to reflect that the "Interactive dialogs" row now includes file input element support on mobile (previously absent), and/or to cross-reference the blocks-based dialog format.
  • Consider adding a note to docs/source/administration-guide/upgrade/important-upgrade-notes.rst that block-based interactive dialog forms and input blocks in interactive messages on mobile require Mattermost server version 11.11.0 or later.

Confidence

Medium — The PR introduces clearly new integration-facing functionality (blocks in interactive dialogs, input blocks in interactive posts, file input on mobile) that is documented at a high level in the existing integration guides. The exact RST pages that need updates are identified from search results. Confidence is medium rather than high because the related server-side PR (mattermost/mattermost#37767) may own the primary developer documentation update; the mobile docs impact is focused on mobile-specific availability and the new minimum server version.

@github-actions github-actions Bot added the Docs/Needed Requires documentation label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Coverage Comparison Report

Generated on August 10, 2026 at 13:52:03 UTC

+-----------------+------------+------------+-----------+
| Metric          | Main       | This PR    | Diff      |
+-----------------+------------+------------+-----------+
| Lines           |     88.33% |     88.46% |     0.13% |
| Statements      |     88.20% |     88.33% |     0.13% |
| Branches        |     77.25% |     77.73% |     0.48% |
| Functions       |     87.63% |     87.79% |     0.16% |
+-----------------+------------+------------+-----------+
| Total           |     85.35% |     85.57% |     0.22% |
+-----------------+------------+------------+-----------+

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds native mm_blocks form inputs, typed dialog actions, validation, uploads, dynamic lookups, native dialog routing, legacy fallbacks, navigation support, and unit, integration, and Detox coverage.

Changes

MM Blocks form contracts and translation

Layer / File(s) Summary
Form schemas and translation
types/api/mm_blocks.d.ts, types/api/integrations.d.ts, app/components/block_renderer/translation/*, app/utils/dialog_to_mm_blocks.ts
The API types now define six form input blocks and block-action responses. Block Kit, Adaptive Cards, and legacy dialogs translate supported inputs into mm_blocks.
Form state and validation
app/components/block_renderer/form/*, app/components/block_renderer/form_validation.ts
Form context, relayed form state, typed value conversion, field errors, tree traversal, input stripping, and field validation are implemented.
Input rendering and uploads
app/components/block_renderer/*, app/components/autocomplete_selector/*, app/components/settings/*
Text, boolean, select, date, datetime, and file inputs render with form state, lookup handling, disabled states, validation errors, chips, date constraints, and upload lifecycle handling.
Dialog actions and routing
app/actions/remote/integrations.ts, app/client/rest/integrations.ts, app/screens/dialog_router/*, app/components/post_list/post/body/content/interactive_messages/*
Typed dialog and block-action requests use feature-gated paths. Native dialogs handle submissions, refreshes, lookups, child dialogs, errors, navigation, uploads, and legacy fallback behaviour.
Navigation and supporting UI
app/screens/mm_blocks_content/*, app/screens/mm_blocks_text_input/*, app/routes/(modals)/*, app/managers/integrations_manager.ts, app/queries/servers/features.ts
Dialog configuration uses CallbackStore. Modal routes support native block content and text input. Expanded content relays form state. Block-action availability requires the configured server version and MM Blocks flag.
Validation and integration tests
app/components/block_renderer/**/*.test.*, app/screens/dialog_router/*.test.tsx, app/actions/remote/*.test.ts, app/client/rest/*.test.ts, app/queries/servers/features.test.ts, app/utils/*.test.ts
Tests cover form controls, translation, validation, uploads, dialog routing, action requests, feature gates, errors, refreshes, stale responses, and navigation.
Detox dialog coverage
detox/e2e/support/*, detox/e2e/test/products/channels/interactive_messages/*, detox/utils/webhook_utils.js, detox/webhook_server.js
Detox helpers, fixtures, webhook routes, and suites cover form inputs, dates, files, selects, validation, multistep dialogs, child dialogs, stacking, refreshes, cancellation, and navigation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant InteractiveMessages
  participant BlockRenderer
  participant BlocksDialogShell
  participant IntegrationClient
  participant IntegrationServer

  User->>InteractiveMessages: Trigger block action
  InteractiveMessages->>BlockRenderer: Render blocks and form state
  BlockRenderer->>BlocksDialogShell: Submit action parameters and form values
  BlocksDialogShell->>IntegrationClient: Send typed block-action request
  IntegrationClient->>IntegrationServer: POST block action
  IntegrationServer-->>IntegrationClient: Return errors, refresh, navigation, or dialog response
  IntegrationClient-->>BlocksDialogShell: Return action response
  BlocksDialogShell-->>User: Update fields, dialog, navigation, or errors
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: adding Blocks support to interactive dialogs.
Description check ✅ Passed The description directly explains Blocks support for interactive dialogs, interactive messages, input types, and UI updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dialogBlocks

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
detox/e2e/support/mm_blocks_test_helper.ts (1)

133-171: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Declare the new Detox testIDs in page objects. All four sites build by.id() matchers from raw string literals instead of page-object constants. InteractiveDialogScreen already exposes a testID map, and the mm_blocks field testIDs have no page-object home yet.

  • detox/e2e/support/mm_blocks_test_helper.ts#L133-L171: replace by.id('interactive_dialog.scroll_view') at lines 135, 163, 166, and 167 with InteractiveDialogScreen.testID.scrollView.
  • detox/e2e/support/mm_blocks_test_helper.ts#L318-L332: replace the same literal at lines 325 and 328 with InteractiveDialogScreen.testID.scrollView.
  • detox/e2e/support/mm_blocks_test_helper.ts#L688-L706: add mm_blocks_text_input.screen, mm_blocks_text_input.input, and mm_blocks.text_input.save.button to a page object and reference those constants.
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts#L29-L30: delete the local SUBMIT_BUTTON and CLOSE_BUTTON constants and use InteractiveDialogScreen.testID; build the child_input matcher from a page-object helper.

As per path instructions: "Never hardcode strings in by.id(); use page-object testID constants instead."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@detox/e2e/support/mm_blocks_test_helper.ts` around lines 133 - 171, Replace
every raw interactive dialog scroll-view ID in
detox/e2e/support/mm_blocks_test_helper.ts at ranges 133-171 and 318-332 with
InteractiveDialogScreen.testID.scrollView; at 688-706, add page-object constants
for mm_blocks_text_input.screen, mm_blocks_text_input.input, and
mm_blocks.text_input.save.button and use them in by.id matchers. In
detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts
at 29-30, remove local SUBMIT_BUTTON and CLOSE_BUTTON constants, use
InteractiveDialogScreen.testID values, and build the child_input matcher through
a page-object helper. Do not leave hardcoded by.id strings.

Source: Path instructions

app/components/settings/label.tsx (1)

18-21: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Preserve RadioSetting label alignment.

RadioSetting renders Label above entries with paddingHorizontal: 16. Removing marginLeft: 15 shifts the label to the container edge and misaligns it. Restore the margin or add a consumer-specific style for mm_blocks labels.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/settings/label.tsx` around lines 18 - 21, Update the Label
styling in the theme configuration to restore the 15px left margin, or apply an
equivalent consumer-specific style for mm_blocks labels, so labels rendered by
RadioSetting align with entries using paddingHorizontal: 16.
🟡 Minor comments (15)
app/actions/remote/integrations.ts-120-121 (1)

120-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize or suppress the user-facing text of this error.

Consumers display error.message to the user. app/components/post_list/post/body/content/interactive_messages/index.tsx sets setActionError(message ?? actionFailedMessage) with message = error.message, and BlocksDialogShell does the same. The hardcoded English string at Line 121 therefore reaches the UI untranslated. Return an error without a display message, so the consumer falls back to its localized message, or add a translated message with defineMessages() at the consumer.

As per coding guidelines: "Define new messages with defineMessages() and run npm run i18n-extract to update en.json."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/actions/remote/integrations.ts` around lines 120 - 121, Update the
doBlockAction-unavailable branch to avoid exposing the hardcoded English Error
message through error.message; return an error without a display message so
consumers such as BlocksDialogShell and the interactive message component use
their localized fallback. Keep the existing debug log and availability behavior
unchanged.

Source: Coding guidelines

app/components/block_renderer/mm_blocks_file_upload.test.tsx-42-43 (1)

42-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use an ICU plural for the upload-limit warning. Update fileMaxWarning() in app/utils/file/index.ts, assets/base/i18n/en.json, and SINGLE_FILE_WARNING so count 1 renders “Uploads limited to 1 file maximum.” Define the message with defineMessages() and run npm run i18n-extract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/mm_blocks_file_upload.test.tsx` around lines 42
- 43, Update fileMaxWarning() in app/utils/file/index.ts to use a
defineMessages() ICU plural message, with count 1 rendered as “Uploads limited
to 1 file maximum.” Update the matching English translation in
assets/base/i18n/en.json and revise SINGLE_FILE_WARNING to the singular wording,
then run npm run i18n-extract.

Source: Coding guidelines

app/components/settings/bool_setting.tsx-138-145 (1)

138-145: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

value can be undefined in the testID and in Switch.

value is an optional prop. When a caller omits it, the testID becomes <testID>.toggled.undefined.button, and Switch becomes uncontrolled. Normalize the value once.

🐛 Proposed fix
+    const checked = value === true;
...
                 <Switch
                     disabled={disabled}
                     onValueChange={onChange}
-                    value={value}
+                    value={checked}
                     trackColor={trackColor}
                     thumbColor={thumbColor}
-                    testID={`${testID}.toggled.${value}.button`}
+                    testID={`${testID}.toggled.${checked}.button`}
                 />

Use checked for thumbColor as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/settings/bool_setting.tsx` around lines 138 - 145, Normalize
the optional value once in the BoolSetting component, then use the normalized
boolean for the Switch value, the toggled testID, and thumbColor via the checked
state. Keep the existing behavior unchanged when value is provided, while
ensuring omitted value never produces undefined or an uncontrolled Switch.
app/components/settings/bool_setting.tsx-117-126 (1)

117-126: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Pass optional explicitly in useBooleanProp

The component-library caller omits optional, so BoolSetting defaults to false and displays the red required marker. Set the intended value explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/settings/bool_setting.tsx` around lines 117 - 126, Update the
component-library caller of BoolSetting to pass optional explicitly as true
through useBooleanProp, ensuring the setting renders the optional label instead
of the required marker. Keep the existing BoolSetting rendering logic unchanged.
app/components/block_renderer/translation/block_kit.ts-160-162 (1)

160-162: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard invalid datetime values before formatting.

A finite initial_date_time can create an invalid Date, so toISOString() throws RangeError. translatePostProps catches this error but replaces the full translation with error blocks. Check date.getTime() before formatting, and add a regression test for an oversized finite timestamp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/translation/block_kit.ts` around lines 160 -
162, Update the initial_date_time handling in translatePostProps to create the
Date, validate date.getTime() is finite before calling toISOString(), and skip
initial_value assignment for invalid dates. Add a regression test covering an
oversized finite timestamp and verify translation returns normally without error
blocks.
app/components/block_renderer/datetime_input_element/datetime_input_element.tsx-122-137 (1)

122-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass displayTimezone to FormattedDate.

FormattedDate accepts timezone. Without it, the date uses the device timezone while FormattedTime uses displayTimezone. Near midnight, the two values can show different dates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/components/block_renderer/datetime_input_element/datetime_input_element.tsx`
around lines 122 - 137, Update the FormattedDate component in the selectedDate
display to pass the existing displayTimezone through its timezone prop, matching
the timezone already supplied to FormattedTime so both values represent the same
date and time zone.
app/components/block_renderer/date_input_element/date_input_element.test.tsx-50-68 (1)

50-68: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Shared onAction mock and database hooks in the new date and datetime suites. Both suites declare one onAction mock and never reset it, and both create the server database in beforeAll instead of beforeEach. A later test in each suite asserts expect(onAction).not.toHaveBeenCalled() after an earlier test called it, so each suite depends on global Jest mock-clearing configuration.

  • app/components/block_renderer/date_input_element/date_input_element.test.tsx#L50-L68: add a beforeEach that calls jest.clearAllMocks() and restores the DateTimeSelector mock implementation, and move the database setup and teardown to beforeEach/afterEach.
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx#L51-L69: apply the same beforeEach reset and the same database lifecycle change.

As per coding guidelines: "Use real in-memory LokiJS databases with autosave: false, initialize them in beforeEach, and destroy server databases in afterEach."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/date_input_element/date_input_element.test.tsx`
around lines 50 - 68, In
app/components/block_renderer/date_input_element/date_input_element.test.tsx
lines 50-68, add a beforeEach that clears Jest mocks and restores the
DateTimeSelector mock implementation, and move database setup from beforeAll to
beforeEach and teardown from afterAll to afterEach. Apply the identical changes
in
app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
lines 51-69, preserving real in-memory LokiJS databases with autosave disabled.

Source: Coding guidelines

detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts-534-548 (1)

534-548: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Confirm the swallowed picker error with failure artifacts.

The catch block hides a real Android picker failure and lets the test continue with the default date. The assertion at line 563 then passes on a date the test never set. Record the evidence that motivated this branch, or split the Android path into an explicit platform-specific flow instead of a silent catch.

As per coding guidelines: "Do not add retry loops, second taps, swallowed exceptions, or increased timeouts to address a failure without completing the artifact-reading checklist and obtaining evidence."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts`
around lines 534 - 548, Update the date-picker setup around
nativeDateTimePicker.setDatePickerDate to avoid silently swallowing Android
failures: either capture and preserve failure artifacts before allowing the
default-date confirmation path, or split Android into an explicit
platform-specific flow that does not rely on a swallowed exception. Keep the
existing iOS dismissal behavior and ensure the assertion cannot pass without
verifying the intended date was set.

Source: Coding guidelines

app/components/block_renderer/form_validation.ts-193-195 (1)

193-195: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the URL subtype check with the legacy dialog check.

includes('http://') accepts a value where the scheme is not at the start, for example ftp://host/?next=http://x. The legacy validator in app/utils/integrations.ts (line 197) uses startsWith for the same subtype. Use the same rule so both dialog paths agree.

🐛 Proposed fix
-            if (field.subtype === 'url' && !stringValue.includes('http://') && !stringValue.includes('https://')) {
+            if (field.subtype === 'url' && !stringValue.startsWith('http://') && !stringValue.startsWith('https://')) {
                 return dialogFieldErrorMessages.badUrl;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/form_validation.ts` around lines 193 - 195,
Update the URL validation condition in the field validation flow to require the
value to start with either “http://” or “https://” using startsWith, matching
the legacy validator in integrations.ts. Replace the current includes checks
while preserving the existing badUrl error response.
app/components/block_renderer/mm_blocks_file_upload.tsx-306-325 (1)

306-325: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Duplicate clientId values can collide when the same file is picked twice.

clientId falls back to file.localPath ?? file.name. In multi-select mode the same file can be added twice. Two rows then share one clientId. patchFile updates both rows, withoutFile removes both rows, and React renders duplicate keys at Line 408. Add a uniqueness guard or suffix.

🐛 Proposed fix
         const added: UploadFileState[] = [];
         for (const file of toUpload) {
-            const clientId = file.clientId ?? file.localPath ?? file.name;
+            const baseId = file.clientId ?? file.localPath ?? file.name;
+            let clientId = baseId;
+            let suffix = 1;
+            while (sourceFilesRef.current.has(clientId) || filesRef.current.some((f) => f.clientId === clientId)) {
+                clientId = `${baseId}-${suffix++}`;
+            }
             sourceFilesRef.current.set(clientId, file);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/mm_blocks_file_upload.tsx` around lines 306 -
325, Update the file-ID generation in the upload handling loop before
`sourceFilesRef.current.set` and `added.push` so each newly added file receives
a unique `clientId`, including repeated selections of the same file in
multi-select mode. Preserve existing IDs when unused, but append a deterministic
suffix or otherwise resolve collisions against current source files and files
being added; use the resolved ID consistently for `sourceFilesRef`, the
`UploadFileState`, and `startUpload`.
app/components/settings/radio_setting/radio_entry.tsx-106-118 (1)

106-118: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a visual disabled state.

disabled blocks the press, but the row keeps full opacity and the normal text colour. Users cannot see that the option is not selectable. Other disabled controls in this PR are greyed out. Apply a dimmed style when disabled is true.

🎨 Proposed fix
         text: {
             flex: 1,
             color: theme.centerChannelColor,
             ...typography('Body', 200),
         },
+        disabled: {
+            opacity: 0.32,
+        },
-                <View style={style.container}>
+                <View style={[style.container, disabled && style.disabled]}>
                     <Text style={style.text}>{text}</Text>
                     {indicator}
                 </View>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/settings/radio_setting/radio_entry.tsx` around lines 106 -
118, Update the row rendered by the radio entry component so its visual style
changes when disabled: apply the established dimmed/greyed styling to the
container or text while preserving the normal styling when enabled. Use the
existing disabled prop and style symbols rather than changing the press
behavior.
detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts-34-51 (1)

34-51: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the per-test navigation with the standard E2E structure.

setupChannelTest() handles Setup.apiInit(siteOneUrl) and the initial channel-list assertion. Add ChannelListScreen.toBeVisible() to beforeEach, return to the channel list in afterEach, and reopen testChannel before each test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts`
around lines 34 - 51, Update the suite lifecycle around beforeEach and
afterEach: in beforeEach, assert ChannelListScreen.toBeVisible() and reopen
testChannel after assertSuiteRunnable(); in afterEach, navigate back to the
channel list instead of only ensuring the channel screen, while preserving
blocks-dialog dismissal and existing setupChannelTest behavior.

Source: Path instructions

app/screens/dialog_router/blocks_dialog_router.test.tsx-63-76 (1)

63-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the explicit undefined argument from both assertions.

React 19 invokes mockBlocksDialogShell with only the props argument. The extra argument causes toHaveBeenCalledWith to fail.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/screens/dialog_router/blocks_dialog_router.test.tsx` around lines 63 -
76, Update both mockBlocksDialogShell assertions to call toHaveBeenCalledWith
with only the expected props object, removing the explicit undefined argument so
they match React 19’s single-argument invocation.
detox/e2e/support/ui/screen/interactive_dialog.ts-12-42 (1)

12-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the modal text input testID in fillTextElement().

textInputFieldTestID() generates mm_blocks.text_input.${elementName}.input, but MmBlocksTextInput renders mm_blocks_text_input.input. Update the helper so text-field actions can find the modal input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@detox/e2e/support/ui/screen/interactive_dialog.ts` around lines 12 - 42,
Update textInputFieldTestID in the interactive dialog screen helper to return
the modal input testID rendered by MmBlocksTextInput, using the
mm_blocks_text_input.input identifier instead of the current
textInputTestID-based value so fillTextElement() can locate the field.

Source: Path instructions

detox/utils/webhook_utils.js-444-446 (1)

444-446: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Lower-case searchText before the comparison.

The option text and value are lower-cased, but searchText is not. A query such as Alpha matches nothing. The new helper getMmBlocksLookupOptions at Line 1715 lower-cases the search term, so the two lookup paths behave differently.

🐛 Proposed fix
-    const filteredOptions = searchText ?baseOptions.filter((option) =>
-        option.text.toLowerCase().includes(searchText) ||
-            option.value.toLowerCase().includes(searchText)) :baseOptions.slice(0, 6); // Limit to first 6 if no search
+    const search = String(searchText || '').toLowerCase();
+    const filteredOptions = search ? baseOptions.filter((option) =>
+        option.text.toLowerCase().includes(search) ||
+        option.value.toLowerCase().includes(search)) : baseOptions.slice(0, 6); // Limit to first 6 if no search
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@detox/utils/webhook_utils.js` around lines 444 - 446, Update the
filteredOptions logic in getMmBlocksLookupOptions to normalize searchText to
lowercase before comparing it with the lowercased option.text and option.value,
preserving case-insensitive matching consistently with the other lookup path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/autocomplete_selector/index.tsx`:
- Around line 342-381: Update the chip removal handlers in the selection
rendering and the related callback near the navigation guard to block removal
when disabled is true. Ensure the callback used by SelectedUserChipById,
SelectedChannelChip, and SelectedChip includes disabled in its dependency array
and preserves removal behavior when enabled.

In `@app/components/block_renderer/bool_input_element.tsx`:
- Around line 27-29: Update the useEffect in the boolean input component to
return immediately when element.name is blank before calling setDefaultValue,
while preserving normal default registration for valid names. Add a regression
test covering an invalid-name field followed by another field action and assert
that formValues never contains an empty-string key.

In `@app/components/block_renderer/date_input_element/date_input_element.tsx`:
- Around line 43-44: Update the date input action flow around
interactionsDisabled and element.disabled so disabled elements return before
calling setValue, while preserving the existing allowed setValue behavior when
only interactionsDisabled is active. Extend DateTimeSelector to accept and
propagate disabled state to its controls, then add coverage verifying disabled
date elements do not update their value.

In `@app/components/block_renderer/file_input_element.tsx`:
- Around line 80-82: Update FileInputElement’s lifecycle handling around
handlePendingChange to clear the field’s uploading state on unmount and whenever
element.name changes: invoke setFieldUploading for the current field with false
in a cleanup effect, while preserving the existing pending-change behavior.

In `@app/components/block_renderer/form/mm_blocks_form.tsx`:
- Around line 56-77: Update setValue and setDefaultValue to compute each next
values map before calling setValues, assign it to valuesRef.current immediately,
and pass the computed map to the state update while preserving no-op checks.
Remove ref mutations from the setValues updater functions so getValues returns
the latest values synchronously and React updater purity is maintained; rely on
the existing render-time ref assignment for alignment after commits.

In `@app/components/block_renderer/select_input_element.tsx`:
- Around line 71-73: The multi-select array is recreated on every render,
causing unnecessary label lookups. In
app/components/block_renderer/select_input_element.tsx lines 71-73, memoize
multiValue so its identity changes only when the underlying raw values or
initial option change; in app/components/autocomplete_selector/index.tsx lines
315-340, key the selectedValues memo and related effect on a joined
representation of the values rather than the array reference.

In `@app/managers/integrations_manager.ts`:
- Around line 62-67: Use a unique per-navigation key when storing the dialog
config in integrations_manager.ts, and pass that key with the title to
Screens.DIALOG_ROUTER. In app/routes/(modals)/dialog_router.tsx, read the same
key and remove only that CallbackStore entry during cleanup instead of removing
the unkeyed global callback; update both affected sites accordingly.

In `@app/screens/navigation.ts`:
- Around line 101-110: Update dismissMmBlocksExpandedContentIfOpen in
app/screens/navigation.ts (lines 101-110) to stop when router.canGoBack() is
false and enforce an iteration cap before calling navigateBack. Add a test in
app/screens/navigation.test.ts (lines 308-348) where getVisibleScreen always
returns Screens.MM_BLOCKS_CONTENT and router.canGoBack returns false, asserting
the promise resolves without calling router.back.

In `@app/utils/dialog_utils.ts`:
- Around line 33-34: Update the Apps Form fallback conversion to filter out
elements whose FILE and ACTION_BUTTON types are defined in the dialog element
constants before converting unsupported elements to text fields. Preserve all
other fallback elements and their existing conversion behavior.

In
`@detox/e2e/test/products/channels/interactive_dialog/interactive_dialog_plugin.e2e.ts`:
- Around line 366-369: Replace all hardcoded interactive-dialog testID strings
used by by.id() throughout this test, including the boolean inputs and the
referenced ranges, with the corresponding InteractiveDialogScreen builders:
boolInputTestID, selectButtonTestID, radioOptionTestID, textInputFieldTestID,
dateInputTestID, and dateTimeInputTestID. Replace scroll-view IDs with
InteractiveDialogScreen.scrollView, preserving the existing assertions and
interactions.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts`:
- Around line 71-73: Replace all hardcoded MM Blocks testID strings in by.id()
with exported constants from the interactive dialog page object. Add constants
to interactive_dialog.ts for the date/datetime, file and error, select, text,
and boolean input IDs, then update
detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts
lines 71-73 and 107-108, mm_blocks_dialog_files.e2e.ts lines 71-93, and
mm_blocks_dialog_multistep.e2e.ts lines 77-88 and 103-115; no affected literal
should remain in these suites.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts`:
- Around line 36-53: Move the testID definitions used by nativeDateTimePicker,
field, and the additional by.id() calls in the test into the relevant exported
page-object constants under support/ui/screen. Update this test to import and
reference those constants instead of constructing matchers from hardcoded ID
strings, preserving each existing selector and behavior.

---

Outside diff comments:
In `@app/components/settings/label.tsx`:
- Around line 18-21: Update the Label styling in the theme configuration to
restore the 15px left margin, or apply an equivalent consumer-specific style for
mm_blocks labels, so labels rendered by RadioSetting align with entries using
paddingHorizontal: 16.

In `@detox/e2e/support/mm_blocks_test_helper.ts`:
- Around line 133-171: Replace every raw interactive dialog scroll-view ID in
detox/e2e/support/mm_blocks_test_helper.ts at ranges 133-171 and 318-332 with
InteractiveDialogScreen.testID.scrollView; at 688-706, add page-object constants
for mm_blocks_text_input.screen, mm_blocks_text_input.input, and
mm_blocks.text_input.save.button and use them in by.id matchers. In
detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts
at 29-30, remove local SUBMIT_BUTTON and CLOSE_BUTTON constants, use
InteractiveDialogScreen.testID values, and build the child_input matcher through
a page-object helper. Do not leave hardcoded by.id strings.

---

Minor comments:
In `@app/actions/remote/integrations.ts`:
- Around line 120-121: Update the doBlockAction-unavailable branch to avoid
exposing the hardcoded English Error message through error.message; return an
error without a display message so consumers such as BlocksDialogShell and the
interactive message component use their localized fallback. Keep the existing
debug log and availability behavior unchanged.

In
`@app/components/block_renderer/date_input_element/date_input_element.test.tsx`:
- Around line 50-68: In
app/components/block_renderer/date_input_element/date_input_element.test.tsx
lines 50-68, add a beforeEach that clears Jest mocks and restores the
DateTimeSelector mock implementation, and move database setup from beforeAll to
beforeEach and teardown from afterAll to afterEach. Apply the identical changes
in
app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
lines 51-69, preserving real in-memory LokiJS databases with autosave disabled.

In
`@app/components/block_renderer/datetime_input_element/datetime_input_element.tsx`:
- Around line 122-137: Update the FormattedDate component in the selectedDate
display to pass the existing displayTimezone through its timezone prop, matching
the timezone already supplied to FormattedTime so both values represent the same
date and time zone.

In `@app/components/block_renderer/form_validation.ts`:
- Around line 193-195: Update the URL validation condition in the field
validation flow to require the value to start with either “http://” or
“https://” using startsWith, matching the legacy validator in integrations.ts.
Replace the current includes checks while preserving the existing badUrl error
response.

In `@app/components/block_renderer/mm_blocks_file_upload.test.tsx`:
- Around line 42-43: Update fileMaxWarning() in app/utils/file/index.ts to use a
defineMessages() ICU plural message, with count 1 rendered as “Uploads limited
to 1 file maximum.” Update the matching English translation in
assets/base/i18n/en.json and revise SINGLE_FILE_WARNING to the singular wording,
then run npm run i18n-extract.

In `@app/components/block_renderer/mm_blocks_file_upload.tsx`:
- Around line 306-325: Update the file-ID generation in the upload handling loop
before `sourceFilesRef.current.set` and `added.push` so each newly added file
receives a unique `clientId`, including repeated selections of the same file in
multi-select mode. Preserve existing IDs when unused, but append a deterministic
suffix or otherwise resolve collisions against current source files and files
being added; use the resolved ID consistently for `sourceFilesRef`, the
`UploadFileState`, and `startUpload`.

In `@app/components/block_renderer/translation/block_kit.ts`:
- Around line 160-162: Update the initial_date_time handling in
translatePostProps to create the Date, validate date.getTime() is finite before
calling toISOString(), and skip initial_value assignment for invalid dates. Add
a regression test covering an oversized finite timestamp and verify translation
returns normally without error blocks.

In `@app/components/settings/bool_setting.tsx`:
- Around line 138-145: Normalize the optional value once in the BoolSetting
component, then use the normalized boolean for the Switch value, the toggled
testID, and thumbColor via the checked state. Keep the existing behavior
unchanged when value is provided, while ensuring omitted value never produces
undefined or an uncontrolled Switch.
- Around line 117-126: Update the component-library caller of BoolSetting to
pass optional explicitly as true through useBooleanProp, ensuring the setting
renders the optional label instead of the required marker. Keep the existing
BoolSetting rendering logic unchanged.

In `@app/components/settings/radio_setting/radio_entry.tsx`:
- Around line 106-118: Update the row rendered by the radio entry component so
its visual style changes when disabled: apply the established dimmed/greyed
styling to the container or text while preserving the normal styling when
enabled. Use the existing disabled prop and style symbols rather than changing
the press behavior.

In `@app/screens/dialog_router/blocks_dialog_router.test.tsx`:
- Around line 63-76: Update both mockBlocksDialogShell assertions to call
toHaveBeenCalledWith with only the expected props object, removing the explicit
undefined argument so they match React 19’s single-argument invocation.

In `@detox/e2e/support/ui/screen/interactive_dialog.ts`:
- Around line 12-42: Update textInputFieldTestID in the interactive dialog
screen helper to return the modal input testID rendered by MmBlocksTextInput,
using the mm_blocks_text_input.input identifier instead of the current
textInputTestID-based value so fillTextElement() can locate the field.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts`:
- Around line 34-51: Update the suite lifecycle around beforeEach and afterEach:
in beforeEach, assert ChannelListScreen.toBeVisible() and reopen testChannel
after assertSuiteRunnable(); in afterEach, navigate back to the channel list
instead of only ensuring the channel screen, while preserving blocks-dialog
dismissal and existing setupChannelTest behavior.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts`:
- Around line 534-548: Update the date-picker setup around
nativeDateTimePicker.setDatePickerDate to avoid silently swallowing Android
failures: either capture and preserve failure artifacts before allowing the
default-date confirmation path, or split Android into an explicit
platform-specific flow that does not rely on a swallowed exception. Keep the
existing iOS dismissal behavior and ensure the assertion cannot pass without
verifying the intended date was set.

In `@detox/utils/webhook_utils.js`:
- Around line 444-446: Update the filteredOptions logic in
getMmBlocksLookupOptions to normalize searchText to lowercase before comparing
it with the lowercased option.text and option.value, preserving case-insensitive
matching consistently with the other lookup path.

---

Nitpick comments:
In `@app/actions/remote/file.ts`:
- Around line 43-52: Update the catch block in fetchFileInfo to call
forceLogoutIfNecessary(serverUrl, error) before logging and returning the error,
matching the error handling used by sibling actions such as fetchPublicLink.

In `@app/client/rest/integrations.test.ts`:
- Around line 98-136: Rename the three newly added tests for
lookupInteractiveDialog, executeDialogAction, and doBlockAction to the
repository’s preferred it('should...') naming style, while preserving their
existing assertions and behavior.

In `@app/components/block_renderer/block_renderer.tsx`:
- Around line 80-82: Prevent redundant notifications in the effect around
hasUploadingFields and onUploadingChange by retaining the last notified boolean
in a ref and invoking the callback only when that value changes; alternatively,
document the required stable callback identity in the onUploadingChange prop
comment. Keep the existing notification behavior for actual hasUploadingFields
transitions.

In
`@app/components/block_renderer/datetime_input_element/datetime_input_element.tsx`:
- Line 129: Move the inline date_time_selector.at descriptor into a module-level
defineMessages() declaration in datetime_input_element, then replace the inline
intl.formatMessage descriptor with intl.formatMessage(messages.at). Run npm run
i18n-extract to update the translation catalog.

In `@app/components/block_renderer/file_input_element.test.tsx`:
- Around line 232-233: Replace the positive remove-control assertion in the file
input test with getByTestId(...).toBeTruthy(), and remove queryByTestId from the
test’s destructured helpers if no other absence assertions use it.

In `@app/components/block_renderer/translation/mm_block.ts`:
- Around line 459-529: Introduce a shared parameterized helper for the
duplicated logic in translateDateInputBlock and translateDateTimeInputBlock,
accepting the appropriate required-keys constant and block type while preserving
each function’s return type and behavior. Update both translators to delegate to
this helper so validation, parsing, and output construction remain centralized.

In
`@app/components/post_list/post/body/content/interactive_messages/index.test.tsx`:
- Around line 415-416: Replace the inline require in the test with a top-level
import of dismissMmBlocksExpandedContentIfOpen from `@screens/navigation`, and use
jest.mocked() when asserting the mocked function. Keep the existing module mock
and assertion behavior unchanged.

In `@app/components/settings/radio_setting/radio_entry.tsx`:
- Around line 83-118: Add accessibilityRole and accessibilityState to the
TouchableWithFeedback option row, using the checklist variant to select the
checkbox role and radio otherwise, and exposing isSelected as checked together
with disabled state.

In `@app/queries/servers/features.test.ts`:
- Around line 127-128: Derive the below-version fixture near atBlockActions from
BLOCK_ACTIONS_VERSION instead of hardcoding 11.10.0, ensuring it always remains
below the configured boundary when the constant changes. Alternatively,
explicitly assert with isMinimumServerVersion that the literal is below
BLOCK_ACTIONS_VERSION.
- Line 110: Rename the newly added tests in the feature flag test block to use
the repository’s “should...” naming convention, including the tests currently
beginning with “honors” and “is” at the referenced locations. Preserve each
test’s behavior and assertions while changing only their descriptions to start
with “should”.

In `@app/queries/servers/features.ts`:
- Line 69: Replace the `version || ''` fallback in the feature checks using
`isMinimumServerVersion` with `version ?? ''`, including the corresponding
occurrence near the second referenced check. Preserve the existing
`mmBlocksEnabled` and version-validation logic.

In `@app/routes/`(modals)/dialog_router.tsx:
- Around line 26-30: Replace the empty-dependency useEffect around
CallbackStore.removeCallback with useDidMount, returning the cleanup function
through that hook so CallbackStore.removeCallback runs on unmount.

In `@app/routes/`(modals)/mm_blocks_text_input.tsx:
- Line 24: Update the text-input modal configuration around headerTitle to
define and reuse the existing mm_blocks.text_input.title message through
defineMessages(), instead of constructing the message inline. Preserve the
existing translated title and fallback text, and do not add a new translation
entry.

In `@app/screens/dialog_router/blocks_dialog_router.test.tsx`:
- Around line 11-16: Update the blocks_dialog_shell mock factory to return an
explicit module object with the expected default export, while preserving the
existing jest.fn implementation that renders the mode-specific test view. Keep
mockBlocksDialogShell aligned with that default-export shape when accessing the
mock.

In `@app/screens/dialog_router/blocks_dialog_shell.test.tsx`:
- Around line 599-623: The upload test around BlockRenderer’s onUploadingChange
callbacks currently repeats the same state and does not verify independent field
tracking. Replace the duplicate true calls with two distinct field names, then
clear each field separately while asserting submit remains disabled until both
uploads finish, and preserve the final successful submit assertion.

In `@app/screens/dialog_router/blocks_dialog_shell.tsx`:
- Around line 693-701: Move the handleLegacyFooterSubmit useCallback declaration
above the derived render values showLegacySubmit and legacyBlockSubmit, grouping
it with the component’s other hooks while preserving its existing callback body
and dependency array.

In `@app/screens/dialog_router/index.test.tsx`:
- Line 50: Replace the CommonJS require for observeBlockActionsEnabled with a
typed top-level import, wrap it using jest.mocked, and update the tests to call
mockedObserveBlockActionsEnabled.mockReturnValue(of$(true)) while preserving the
existing mock behavior.

In `@app/store/navigation_store.ts`:
- Around line 186-190: Document or test the duplicate-handling behavior of
getScreensInStack(): stack navigators must preserve repeated screen IDs, while
tab navigators must process only the active tab and de-duplicate IDs. Anchor the
change to getScreensInStack and cover both navigator modes without altering the
existing positional stack behavior.

In `@app/utils/dialog_conversion.test.ts`:
- Line 918: The test fixtures in app/utils/dialog_conversion.test.ts:918-918 and
app/utils/interactive_dialog_adapter.test.ts:175-175 should be declared as
InteractiveDialogConfig & {dialog: Dialog}; remove the non-null assertions at
dialog_conversion.test.ts lines 918, 932, 946, 960, 1027, 1029, 1062, and 1079,
and interactive_dialog_adapter.test.ts lines 175, 374, and 405, relying on the
strengthened fixture type instead.

In `@app/utils/dialog_conversion.ts`:
- Around line 88-91: The value conversion branch currently treats non-empty
"off" as true; update the field-value handling around submission[fieldName] so
the literal "off" follows the same falsy path as the corresponding false values,
while preserving "on" as truthy and existing Boolean coercion for other
noncanonical strings.

In `@app/utils/dialog_to_mm_blocks.test.ts`:
- Around line 198-221: Merge the two action-button-only cases around
convertDialogToMmBlocks and dialogShouldShowSubmitChrome into one test, reusing
the shared elements and blocks assertions while checking both undefined and
'Save' submit-label outcomes without calling convertDialogToMmBlocks twice.
- Line 66: Rename the affected test cases in the dialog-to-MM-blocks test suite
to begin with “should”, including the cases currently named maps, converts,
keeps, coerces, skips, and labels. Preserve each test’s existing behavior and
assertions while applying the naming convention consistently.

In `@app/utils/dialog_to_mm_blocks.ts`:
- Around line 18-33: Update boolDefault to trim the string value before
lowercasing and comparing it, so surrounding whitespace is ignored consistently
with convertAppFormValuesToDialogSubmission. Preserve the existing handling for
undefined, empty, boolean, and unrecognized values.

In `@assets/base/i18n/en.json`:
- Line 116: Rename the unused apps.error.form.required_fields_empty translation
key in en.json to the interactive_dialog.* message ID used by both interactive
form components, preserving the existing English message value.

In `@detox/e2e/support/ui/screen/interactive_dialog.ts`:
- Around line 97-110: Update InteractiveDialog.submit so the legacy fallback is
used only when the native submit element is absent: use waitFor(...).toExist()
with a short timeout for the existence check, then tap the selected button while
allowing tap or enabled-state failures to propagate. Do not catch every
native-button error, add retries, perform a second tap after a failed tap, or
increase timeouts; remove the fallback if supported builds no longer render
legacySubmitButton.
- Around line 13-14: Remove the duplicate scroll-view identifier from the
selector definitions, keeping a single canonical `scrollView` entry and updating
`interactiveDialogScreen` usages to reference it, or define
`interactiveDialogScreen` as an explicit alias with a clarifying comment. Apply
the same change to both duplicated selector pairs.

In
`@detox/e2e/test/products/channels/interactive_dialog/interactive_dialog_plugin.e2e.ts`:
- Line 590: Replace the raw 3000-millisecond timeout in the waits around
dynamicCompaniesButton, including the occurrences near the referenced lines,
with the shared timeouts.THREE_SEC constant; use timeouts.TWO_SEC or the closest
defined shared constant for any corresponding 2000-millisecond waits. Preserve
the existing wait behavior.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts`:
- Around line 20-22: Update the comment adjacent to EVENT_DATE and MEETING_DATE
so it accurately describes the date-only string constants, and move the “Midday
UTC keeps the calendar day stable” explanation to the T12:00:00Z construction at
the later date-parsing or datetime setup lines.

In
`@detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_selects.e2e.ts`:
- Around line 24-25: Replace the any annotations on the shared testChannel and
testUser fixtures with types derived from the relevant helper return values,
using ReturnType or the helpers’ awaited result types as appropriate. Preserve
their existing initialization and usage while enabling TypeScript to validate
properties such as display_name and username.
- Around line 27-56: Update the test lifecycle around setupChannelTest and the
beforeEach/afterEach hooks: document that setupChannelTest wraps
Setup.apiInit(siteOneUrl) and the initial channel-list visibility assertion, run
the isolated setup in beforeEach, and explicitly assert
ChannelListScreen.toBeVisible() before each test. Change afterEach cleanup to
return to ChannelListScreen rather than ChannelScreen, while preserving the
existing dialog dismissal and recovery handling.

In `@types/api/integrations.d.ts`:
- Around line 224-227: Replace the duplicate DialogSelectOption object
definition with a type alias to the existing DialogOption type, preserving the
current public name while ensuring both option types cannot diverge.
- Line 140: Review the block payload types in BlockDialog.blocks and
DoBlockActionResponse.mm_blocks, using the existing MmBlock type where consumers
treat these values as MmBlock[]. Replace the loose array declarations and
corresponding direct casts when appropriate, while retaining the wire-level
types if boundary narrowing is intentional.

In `@types/api/mm_blocks.d.ts`:
- Around line 157-158: Update the data_source type in the relevant declaration
to use the specific literals alongside an open-ended `(string & {})` arm,
preserving arbitrary custom strings while retaining editor suggestions for
users, channels, and dynamic.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71d1bc80-5996-4220-9e1b-95e9ee573c7e

📥 Commits

Reviewing files that changed from the base of the PR and between 06ed862 and 0864785.

📒 Files selected for processing (119)
  • app/actions/remote/file.test.ts
  • app/actions/remote/file.ts
  • app/actions/remote/integrations.test.ts
  • app/actions/remote/integrations.ts
  • app/client/rest/files.test.ts
  • app/client/rest/files.ts
  • app/client/rest/integrations.test.ts
  • app/client/rest/integrations.ts
  • app/components/autocomplete_selector/index.test.tsx
  • app/components/autocomplete_selector/index.tsx
  • app/components/block_renderer/block_renderer.test.tsx
  • app/components/block_renderer/block_renderer.tsx
  • app/components/block_renderer/bool_input_element.test.tsx
  • app/components/block_renderer/bool_input_element.tsx
  • app/components/block_renderer/button_element.test.tsx
  • app/components/block_renderer/button_element.tsx
  • app/components/block_renderer/context.tsx
  • app/components/block_renderer/date_input_element/date_input_element.test.tsx
  • app/components/block_renderer/date_input_element/date_input_element.tsx
  • app/components/block_renderer/date_input_element/index.ts
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.tsx
  • app/components/block_renderer/datetime_input_element/index.ts
  • app/components/block_renderer/file_input_element.test.tsx
  • app/components/block_renderer/file_input_element.tsx
  • app/components/block_renderer/form/context.ts
  • app/components/block_renderer/form/form.test.tsx
  • app/components/block_renderer/form/index.ts
  • app/components/block_renderer/form/mm_blocks_field_error.tsx
  • app/components/block_renderer/form/mm_blocks_form.tsx
  • app/components/block_renderer/form/relayed_mm_blocks_form.tsx
  • app/components/block_renderer/form/types.ts
  • app/components/block_renderer/form_validation.test.ts
  • app/components/block_renderer/form_validation.ts
  • app/components/block_renderer/index.ts
  • app/components/block_renderer/layout_blocks.tsx
  • app/components/block_renderer/mm_blocks_context_provider.test.tsx
  • app/components/block_renderer/mm_blocks_context_provider.tsx
  • app/components/block_renderer/mm_blocks_file_upload.test.tsx
  • app/components/block_renderer/mm_blocks_file_upload.tsx
  • app/components/block_renderer/select_input_element.test.tsx
  • app/components/block_renderer/select_input_element.tsx
  • app/components/block_renderer/static_select_element.test.tsx
  • app/components/block_renderer/static_select_element.tsx
  • app/components/block_renderer/text_input_element.test.tsx
  • app/components/block_renderer/text_input_element.tsx
  • app/components/block_renderer/translation/adaptive_cards.test.ts
  • app/components/block_renderer/translation/adaptive_cards.ts
  • app/components/block_renderer/translation/block_kit.test.ts
  • app/components/block_renderer/translation/block_kit.ts
  • app/components/block_renderer/translation/mm_block.test.ts
  • app/components/block_renderer/translation/mm_block.ts
  • app/components/block_renderer/types.ts
  • app/components/chips/selected_channel_chip.tsx
  • app/components/floating_input/floating_input_container.tsx
  • app/components/floating_input/floating_text_input_label.tsx
  • app/components/post_list/index.ts
  • app/components/post_list/post/body/content/content.test.tsx
  • app/components/post_list/post/body/content/interactive_messages/index.test.tsx
  • app/components/post_list/post/body/content/interactive_messages/index.tsx
  • app/components/settings/bool_setting.tsx
  • app/components/settings/footer.tsx
  • app/components/settings/label.test.tsx
  • app/components/settings/label.tsx
  • app/components/settings/radio_setting/index.tsx
  • app/components/settings/radio_setting/radio_entry.tsx
  • app/components/settings/text_setting.tsx
  • app/constants/integrations.ts
  • app/constants/screens.ts
  • app/constants/versions.ts
  • app/managers/integrations_manager.ts
  • app/queries/servers/features.test.ts
  • app/queries/servers/features.ts
  • app/routes/(modals)/dialog_router.tsx
  • app/routes/(modals)/mm_blocks_content.tsx
  • app/routes/(modals)/mm_blocks_text_input.tsx
  • app/screens/apps_form/apps_form_field/apps_form_field.tsx
  • app/screens/component_library/mm_blocks.cl.tsx
  • app/screens/component_library/mm_blocks_editor_utils.ts
  • app/screens/dialog_router/blocks_dialog_router.test.tsx
  • app/screens/dialog_router/blocks_dialog_router.tsx
  • app/screens/dialog_router/blocks_dialog_shell.test.tsx
  • app/screens/dialog_router/blocks_dialog_shell.tsx
  • app/screens/dialog_router/dialog_router.tsx
  • app/screens/dialog_router/index.test.tsx
  • app/screens/dialog_router/index.tsx
  • app/screens/integration_selector/integration_selector.tsx
  • app/screens/mm_blocks_content/index.test.tsx
  • app/screens/mm_blocks_content/index.tsx
  • app/screens/mm_blocks_text_input/index.test.tsx
  • app/screens/mm_blocks_text_input/index.tsx
  • app/screens/navigation.test.ts
  • app/screens/navigation.ts
  • app/store/navigation_store.ts
  • app/utils/dialog_conversion.test.ts
  • app/utils/dialog_conversion.ts
  • app/utils/dialog_to_mm_blocks.test.ts
  • app/utils/dialog_to_mm_blocks.ts
  • app/utils/dialog_utils.ts
  • app/utils/integrations.test.ts
  • app/utils/integrations.ts
  • app/utils/interactive_dialog_adapter.test.ts
  • app/utils/interactive_dialog_adapter.ts
  • assets/base/i18n/en.json
  • detox/e2e/support/mm_blocks_test_helper.ts
  • detox/e2e/support/ui/screen/interactive_dialog.ts
  • detox/e2e/test/products/channels/interactive_dialog/interactive_dialog_plugin.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_files.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_multistep.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_selects.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_validation.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts
  • detox/utils/webhook_utils.js
  • detox/webhook_server.js
  • types/api/integrations.d.ts
  • types/api/mm_blocks.d.ts

Comment thread app/components/autocomplete_selector/index.tsx
Comment thread app/components/block_renderer/bool_input_element.tsx
Comment thread app/components/block_renderer/file_input_element.tsx
Comment thread app/components/block_renderer/form/mm_blocks_form.tsx
Comment thread app/screens/navigation.ts
Comment thread app/utils/dialog_utils.ts
@mattermost-build mattermost-build added the E2E/Run Triggers E2E tests on both iOS and Android via Matterwick label Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/components/date_time_selector/date_time_selector.test.tsx (1)

206-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the disabled time-button path.

This test only presses ${testID}.select.button. Add an assertion for ${testID}.time.button and verify that pressing it does not open the picker or manual time input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/date_time_selector/date_time_selector.test.tsx` around lines
206 - 212, Extend the test around the disabled date selector to also query
`${testID}.time.button`, assert it is disabled, press it, and verify neither the
date-time picker nor manual time input opens. Keep the existing date-button and
mockHandleChange assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/date_time_selector/date_time_selector.test.tsx`:
- Line 194: Rename the test case in the date-time selector test suite so its
it() description starts with “should”, while preserving its existing description
of disabled select buttons and picker behavior.

In `@app/components/date_time_selector/date_time_selector.tsx`:
- Line 220: Update the date-time selector component’s disabled-state handling to
reset both useManualEntry and show whenever disabled becomes true, so
re-enabling requires a new interaction; add a test covering the disable/enable
transition while the manual input or picker is open.

---

Nitpick comments:
In `@app/components/date_time_selector/date_time_selector.test.tsx`:
- Around line 206-212: Extend the test around the disabled date selector to also
query `${testID}.time.button`, assert it is disabled, press it, and verify
neither the date-time picker nor manual time input opens. Keep the existing
date-button and mockHandleChange assertions unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4edc907d-9f5c-4c02-bb55-36447a066b4a

📥 Commits

Reviewing files that changed from the base of the PR and between 0864785 and 0c3ee98.

📒 Files selected for processing (8)
  • app/components/block_renderer/date_input_element/date_input_element.test.tsx
  • app/components/block_renderer/date_input_element/date_input_element.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.tsx
  • app/components/date_time_selector/date_time_selector.test.tsx
  • app/components/date_time_selector/date_time_selector.tsx
  • app/screens/apps_form/apps_form_field/apps_form_field.tsx
  • detox/e2e/support/ui/screen/scheduled_message_screen.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/components/block_renderer/date_input_element/date_input_element.test.tsx
  • app/components/block_renderer/date_input_element/date_input_element.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx

Comment thread app/components/date_time_selector/date_time_selector.test.tsx Outdated
Comment thread app/components/date_time_selector/date_time_selector.tsx
@mattermost-build mattermost-build removed the E2E/Run Triggers E2E tests on both iOS and Android via Matterwick label Aug 7, 2026
@mattermost-build mattermost-build added the E2E/Run Triggers E2E tests on both iOS and Android via Matterwick label Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/date_time_selector/date_time_selector.test.tsx (1)

194-264: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test both disabled interaction states.

Line 220 does not test manual entry. allowManualTimeEntry is false, so the input is absent without a button press.

Lines 249-263 do not open DateTimePicker before disabling. Add a separate disable and re-enable transition that opens the date picker first. Set allowManualTimeEntry={true} in the disabled-button test to verify that the time button cannot reveal manual entry.

As per coding guidelines, test actual implementation behaviour.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/date_time_selector/date_time_selector.test.tsx` around lines
194 - 264, The DateTimeSelector tests do not cover both disabled interaction
states. Update the disabled-button test to set allowManualTimeEntry={true} so
pressing the disabled time button verifies manual entry remains hidden, and
extend the reset test to open the date picker before disabling, then verify it
closes and stays closed after re-enabling.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/block_renderer/mm_blocks_file_upload.tsx`:
- Around line 307-319: Ensure client IDs remain unique for the entire component
lifetime, not only while entries exist in sourceFilesRef, by tracking every
issued ID in a separate persistent ref or associating callbacks with upload
generations. Update the ID allocation and delayed upload callbacks around
sourceFilesRef so callbacks from removed or replaced uploads cannot mutate a
newly selected file, and add a test covering removal, reselection, and
completion of the stale request.

---

Outside diff comments:
In `@app/components/date_time_selector/date_time_selector.test.tsx`:
- Around line 194-264: The DateTimeSelector tests do not cover both disabled
interaction states. Update the disabled-button test to set
allowManualTimeEntry={true} so pressing the disabled time button verifies manual
entry remains hidden, and extend the reset test to open the date picker before
disabling, then verify it closes and stays closed after re-enabling.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be4797f1-159c-4029-ba2d-3dd5780c797c

📥 Commits

Reviewing files that changed from the base of the PR and between 0c3ee98 and 24e5c51.

📒 Files selected for processing (64)
  • app/actions/remote/file.ts
  • app/client/rest/integrations.test.ts
  • app/components/autocomplete_selector/index.tsx
  • app/components/block_renderer/block_renderer.test.tsx
  • app/components/block_renderer/block_renderer.tsx
  • app/components/block_renderer/bool_input_element.test.tsx
  • app/components/block_renderer/bool_input_element.tsx
  • app/components/block_renderer/date_input_element/date_input_element.test.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
  • app/components/block_renderer/datetime_input_element/datetime_input_element.tsx
  • app/components/block_renderer/file_input_element.test.tsx
  • app/components/block_renderer/file_input_element.tsx
  • app/components/block_renderer/form/mm_blocks_form.tsx
  • app/components/block_renderer/form_validation.test.ts
  • app/components/block_renderer/form_validation.ts
  • app/components/block_renderer/mm_blocks_file_upload.test.tsx
  • app/components/block_renderer/mm_blocks_file_upload.tsx
  • app/components/block_renderer/select_input_element.tsx
  • app/components/block_renderer/translation/block_kit.test.ts
  • app/components/block_renderer/translation/block_kit.ts
  • app/components/block_renderer/translation/mm_block.ts
  • app/components/date_time_selector/date_time_selector.test.tsx
  • app/components/date_time_selector/date_time_selector.tsx
  • app/components/post_list/post/body/content/interactive_messages/index.test.tsx
  • app/components/post_list/post/body/content/interactive_messages/index.tsx
  • app/components/settings/bool_setting.tsx
  • app/components/settings/radio_setting/radio_entry.tsx
  • app/queries/servers/features.test.ts
  • app/queries/servers/features.ts
  • app/routes/(modals)/mm_blocks_text_input.tsx
  • app/screens/component_library/hooks.tsx
  • app/screens/dialog_router/blocks_dialog_router.test.tsx
  • app/screens/dialog_router/blocks_dialog_router.tsx
  • app/screens/dialog_router/blocks_dialog_shell.test.tsx
  • app/screens/dialog_router/blocks_dialog_shell.tsx
  • app/screens/dialog_router/index.test.tsx
  • app/screens/edit_post/edit_post.test.tsx
  • app/screens/navigation.test.ts
  • app/screens/navigation.ts
  • app/store/navigation_store.test.ts
  • app/store/navigation_store.ts
  • app/utils/dialog_conversion.test.ts
  • app/utils/dialog_conversion.ts
  • app/utils/dialog_to_mm_blocks.test.ts
  • app/utils/dialog_to_mm_blocks.ts
  • app/utils/file/index.test.ts
  • app/utils/file/index.ts
  • app/utils/interactive_dialog_adapter.test.ts
  • assets/base/i18n/en.json
  • detox/e2e/support/mm_blocks_test_helper.ts
  • detox/e2e/support/ui/screen/index.ts
  • detox/e2e/support/ui/screen/interactive_dialog.ts
  • detox/e2e/support/ui/screen/mm_blocks_text_input.ts
  • detox/e2e/test/products/channels/interactive_dialog/interactive_dialog_plugin.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_files.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_multistep.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_selects.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts
  • detox/utils/webhook_utils.js
  • types/api/integrations.d.ts
  • types/api/mm_blocks.d.ts
🚧 Files skipped from review as they are similar to previous changes (44)
  • app/screens/navigation.test.ts
  • app/screens/navigation.ts
  • app/screens/dialog_router/blocks_dialog_router.tsx
  • app/components/block_renderer/mm_blocks_file_upload.test.tsx
  • app/components/block_renderer/bool_input_element.test.tsx
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_datetime.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_multistep.e2e.ts
  • app/utils/dialog_conversion.test.ts
  • app/screens/dialog_router/blocks_dialog_shell.test.tsx
  • app/store/navigation_store.ts
  • app/components/block_renderer/form_validation.test.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog.e2e.ts
  • app/queries/servers/features.ts
  • app/client/rest/integrations.test.ts
  • app/routes/(modals)/mm_blocks_text_input.tsx
  • app/screens/dialog_router/index.test.tsx
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_files.e2e.ts
  • app/components/settings/bool_setting.tsx
  • app/components/block_renderer/form/mm_blocks_form.tsx
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_stacking.e2e.ts
  • app/components/block_renderer/datetime_input_element/datetime_input_element.tsx
  • app/components/block_renderer/file_input_element.test.tsx
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_dialog_selects.e2e.ts
  • detox/e2e/test/products/channels/interactive_messages/mm_blocks_form_inputs.e2e.ts
  • app/components/block_renderer/bool_input_element.tsx
  • app/utils/dialog_conversion.ts
  • app/components/block_renderer/block_renderer.test.tsx
  • app/components/block_renderer/select_input_element.tsx
  • app/components/block_renderer/translation/block_kit.ts
  • types/api/integrations.d.ts
  • app/components/block_renderer/datetime_input_element/datetime_input_element.test.tsx
  • app/components/block_renderer/date_input_element/date_input_element.test.tsx
  • types/api/mm_blocks.d.ts
  • app/utils/dialog_to_mm_blocks.ts
  • app/components/autocomplete_selector/index.tsx
  • app/actions/remote/file.ts
  • app/components/block_renderer/translation/mm_block.ts
  • app/components/block_renderer/block_renderer.tsx
  • app/components/settings/radio_setting/radio_entry.tsx
  • detox/e2e/support/ui/screen/interactive_dialog.ts
  • app/components/block_renderer/file_input_element.tsx
  • app/components/block_renderer/translation/block_kit.test.ts
  • detox/utils/webhook_utils.js
  • detox/e2e/support/mm_blocks_test_helper.ts

Comment on lines +307 to +319
const usedClientIds = new Set(sourceFilesRef.current.keys());
for (const file of toUpload) {
// Add uniqueness by using a suffix in case the same file is selected multiple times.
let clientId = file.clientId ?? file.localPath ?? file.name;
if (usedClientIds.has(clientId)) {
let suffix = 1;
while (usedClientIds.has(`${clientId}-${suffix}`)) {
suffix += 1;
}
clientId = `${clientId}-${suffix}`;
}
usedClientIds.add(clientId);
sourceFilesRef.current.set(clientId, file);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep client IDs unique for the component lifetime.

If a user removes or replaces an in-flight file and then selects the same source again, Lines 307-319 can reuse its client ID. A delayed callback from the cancelled upload can then mark the new row as failed or uploaded with the old file ID. FileInputElement publishes these settled IDs into form state.

Keep issued IDs in a separate ref, or attach an upload generation to each callback. Add a test that removes an in-flight file, reselects it, and completes the old request after the new request starts.

Proposed fix
 const sourceFilesRef = useRef(new Map<string, ExtractedFileInfo>());
+const issuedClientIdsRef = useRef(new Set<string>());
 
-const usedClientIds = new Set(sourceFilesRef.current.keys());
+const usedClientIds = issuedClientIdsRef.current;

Based on the supplied downstream contract in app/components/block_renderer/file_input_element.tsx, settled IDs become form values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const usedClientIds = new Set(sourceFilesRef.current.keys());
for (const file of toUpload) {
// Add uniqueness by using a suffix in case the same file is selected multiple times.
let clientId = file.clientId ?? file.localPath ?? file.name;
if (usedClientIds.has(clientId)) {
let suffix = 1;
while (usedClientIds.has(`${clientId}-${suffix}`)) {
suffix += 1;
}
clientId = `${clientId}-${suffix}`;
}
usedClientIds.add(clientId);
sourceFilesRef.current.set(clientId, file);
const issuedClientIdsRef = useRef(new Set<string>());
const usedClientIds = issuedClientIdsRef.current;
for (const file of toUpload) {
// Add uniqueness by using a suffix in case the same file is selected multiple times.
let clientId = file.clientId ?? file.localPath ?? file.name;
if (usedClientIds.has(clientId)) {
let suffix = 1;
while (usedClientIds.has(`${clientId}-${suffix}`)) {
suffix += 1;
}
clientId = `${clientId}-${suffix}`;
}
usedClientIds.add(clientId);
sourceFilesRef.current.set(clientId, file);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/block_renderer/mm_blocks_file_upload.tsx` around lines 307 -
319, Ensure client IDs remain unique for the entire component lifetime, not only
while entries exist in sourceFilesRef, by tracking every issued ID in a separate
persistent ref or associating callbacks with upload generations. Update the ID
allocation and delayed upload callbacks around sourceFilesRef so callbacks from
removed or replaced uploads cannot mutate a newly selected file, and add a test
covering removal, reselection, and completion of the stale request.

@mattermost-build mattermost-build added E2E/Run Triggers E2E tests on both iOS and Android via Matterwick and removed E2E/Run Triggers E2E tests on both iOS and Android via Matterwick labels Aug 10, 2026
@yasserfaraazkhan

Copy link
Copy Markdown
Contributor

Hi Daniel,
for now these mm_blocks tests are blocked in CI due to infra limitation. The matterwick cloud server cannot access the webhook run within github runner. We can make sure these tests work locally. So in future (after the work in this ticket supports) these tests will start execution in CI

@larkox

larkox commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

After discussing with Yasser, we are going to try to move the detox tests to use the demo plugin instead of the webhook sidecar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs/Needed Requires documentation release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants