From df9005d64cb8dd597c9eb6b345f64b5b1fdb0296 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 6 Jan 2026 01:16:25 +0100 Subject: [PATCH] fix(ui): handle slack config snapshot --- CHANGELOG.md | 1 + ui/src/ui/controllers/config.test.ts | 139 +++++++++++++++++++++++++++ ui/src/ui/controllers/config.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 ui/src/ui/controllers/config.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4942cab..5d24ddf77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Heartbeat: make HEARTBEAT_OK ack padding configurable across heartbeat and cron delivery. (#238) — thanks @jalehman - WhatsApp: set sender E.164 for direct chats so owner commands work in DMs. - Slack: keep auto-replies in the original thread when responding to thread messages. Thanks @scald for PR #251. +- Control UI: avoid Slack config ReferenceError by reading slack config snapshots. Thanks @sreekaransrinath for PR #249. ### Maintenance - Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome. diff --git a/ui/src/ui/controllers/config.test.ts b/ui/src/ui/controllers/config.test.ts new file mode 100644 index 000000000..f8ee03dc0 --- /dev/null +++ b/ui/src/ui/controllers/config.test.ts @@ -0,0 +1,139 @@ +import { describe, expect, it } from "vitest"; + +import { applyConfigSnapshot, type ConfigState } from "./config"; +import { + defaultDiscordActions, + defaultSlackActions, + type DiscordForm, + type IMessageForm, + type SignalForm, + type SlackForm, + type TelegramForm, +} from "../ui-types"; + +const baseTelegramForm: TelegramForm = { + token: "", + requireMention: true, + allowFrom: "", + proxy: "", + webhookUrl: "", + webhookSecret: "", + webhookPath: "", +}; + +const baseDiscordForm: DiscordForm = { + enabled: true, + token: "", + dmEnabled: true, + allowFrom: "", + groupEnabled: false, + groupChannels: "", + mediaMaxMb: "", + historyLimit: "", + textChunkLimit: "", + replyToMode: "off", + guilds: [], + actions: { ...defaultDiscordActions }, + slashEnabled: false, + slashName: "", + slashSessionPrefix: "", + slashEphemeral: true, +}; + +const baseSlackForm: SlackForm = { + enabled: true, + botToken: "", + appToken: "", + dmEnabled: true, + allowFrom: "", + groupEnabled: false, + groupChannels: "", + mediaMaxMb: "", + textChunkLimit: "", + reactionNotifications: "own", + reactionAllowlist: "", + slashEnabled: false, + slashName: "", + slashSessionPrefix: "", + slashEphemeral: true, + actions: { ...defaultSlackActions }, + channels: [], +}; + +const baseSignalForm: SignalForm = { + enabled: true, + account: "", + httpUrl: "", + httpHost: "", + httpPort: "", + cliPath: "", + autoStart: true, + receiveMode: "", + ignoreAttachments: false, + ignoreStories: false, + sendReadReceipts: false, + allowFrom: "", + mediaMaxMb: "", +}; + +const baseIMessageForm: IMessageForm = { + enabled: true, + cliPath: "", + dbPath: "", + service: "auto", + region: "", + allowFrom: "", + includeAttachments: false, + mediaMaxMb: "", +}; + +function createState(): ConfigState { + return { + client: null, + connected: false, + configLoading: false, + configRaw: "", + configValid: null, + configIssues: [], + configSaving: false, + configSnapshot: null, + configSchema: null, + configSchemaVersion: null, + configSchemaLoading: false, + configUiHints: {}, + configForm: null, + configFormDirty: false, + configFormMode: "form", + lastError: null, + telegramForm: { ...baseTelegramForm }, + discordForm: { ...baseDiscordForm }, + slackForm: { ...baseSlackForm }, + signalForm: { ...baseSignalForm }, + imessageForm: { ...baseIMessageForm }, + telegramConfigStatus: null, + discordConfigStatus: null, + slackConfigStatus: null, + signalConfigStatus: null, + imessageConfigStatus: null, + }; +} + +describe("applyConfigSnapshot", () => { + it("handles missing slack config without throwing", () => { + const state = createState(); + applyConfigSnapshot(state, { + config: { + telegram: {}, + discord: {}, + signal: {}, + imessage: {}, + }, + valid: true, + issues: [], + raw: "{}", + }); + + expect(state.slackForm.botToken).toBe(""); + expect(state.slackForm.actions).toEqual(defaultSlackActions); + }); +}); diff --git a/ui/src/ui/controllers/config.ts b/ui/src/ui/controllers/config.ts index afe4b88b5..760b1d452 100644 --- a/ui/src/ui/controllers/config.ts +++ b/ui/src/ui/controllers/config.ts @@ -100,6 +100,7 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot const config = snapshot.config ?? {}; const telegram = (config.telegram ?? {}) as Record; const discord = (config.discord ?? {}) as Record; + const slack = (config.slack ?? {}) as Record; const signal = (config.signal ?? {}) as Record; const imessage = (config.imessage ?? {}) as Record; const toList = (value: unknown) =>