From e85bda87be8d2d4112647912fad53a5a23ae07a5 Mon Sep 17 00:00:00 2001 From: Tony Dehnke Date: Sat, 21 Feb 2026 13:30:42 +0000 Subject: [PATCH] =?UTF-8?q?fix(mattermost):=20restore=202D=E2=86=921D=20bu?= =?UTF-8?q?tton=20flattening=20and=20empty-name=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core sends buttons as Array> (2D for Telegram row layout). The consolidation from #18151 into #19957 lost the flatMap that flattens to 1D and the .filter() that drops malformed buttons. Without flatMap, each "button" is actually a row array — btn.text is undefined, producing empty-name buttons that render as white boxes with a blue left border in Mattermost. --- extensions/mattermost/src/channel.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/extensions/mattermost/src/channel.ts b/extensions/mattermost/src/channel.ts index b151ed708d9..401ddabf38b 100644 --- a/extensions/mattermost/src/channel.ts +++ b/extensions/mattermost/src/channel.ts @@ -167,15 +167,24 @@ const mattermostMessageActions: ChannelMessageActionAdapter = { if (account.botToken) setInteractionSecret(account.botToken); const callbackUrl = resolveInteractionCallbackUrl(account.accountId, cfg); - const buttons = (params.buttons as Array>).map((btn) => ({ - id: String(btn.id ?? btn.callback_data ?? ""), - name: String(btn.text ?? btn.name ?? btn.label ?? ""), - style: (btn.style as "default" | "primary" | "danger") ?? "default", - context: - typeof btn.context === "object" && btn.context !== null - ? (btn.context as Record) - : undefined, - })); + // Flatten 2D array (rows of buttons) to 1D — core schema sends Array> + // but Mattermost doesn't have row layout, so we flatten all rows into a single list. + // Also supports 1D arrays for backward compatibility. + const rawButtons = (params.buttons as Array).flatMap((item) => + Array.isArray(item) ? item : [item], + ) as Array>; + + const buttons = rawButtons + .map((btn) => ({ + id: String(btn.id ?? btn.callback_data ?? ""), + name: String(btn.text ?? btn.name ?? btn.label ?? ""), + style: (btn.style as "default" | "primary" | "danger") ?? "default", + context: + typeof btn.context === "object" && btn.context !== null + ? (btn.context as Record) + : undefined, + })) + .filter((btn) => btn.id && btn.name); const attachmentText = typeof params.attachmentText === "string" ? params.attachmentText : undefined;