Skip to content

fix(web,server): show real platform connection status in Settings (closes #1031)#1061

Merged
Wirasm merged 1 commit intocoleam00:devfrom
liorfranko:fix/platform-connections-status
Apr 21, 2026
Merged

fix(web,server): show real platform connection status in Settings (closes #1031)#1061
Wirasm merged 1 commit intocoleam00:devfrom
liorfranko:fix/platform-connections-status

Conversation

@liorfranko
Copy link
Copy Markdown
Contributor

@liorfranko liorfranko commented Apr 10, 2026

Summary

  • Problem: The Settings page's "Platform Connections" section hardcoded every platform except Web to "Not configured", so users couldn't tell whether their Slack/Telegram/Discord/GitHub/Gitea/GitLab adapters had actually started.
  • Why it matters: Health/UX — config errors go undetected until a message arrives (or doesn't).
  • What changed:
    • Server: /api/health now returns an activePlatforms array populated live as each adapter's start() resolves. The live reference is passed into registerApiRoutes because Telegram starts after the HTTP listener is already accepting requests, so a snapshot would miss it.
    • Web: SettingsPage.PlatformConnectionsSection reads activePlatforms and looks each platform up in a Set. Also adds Gitea and GitLab to the list (they already ship as adapters).
  • What did not change (scope boundary): No changes to the Telegram adapter — the telegraf/grammY startup semantics fix that was part of the original PR is no longer needed since dev migrated to grammY and now uses the onStart callback in packages/adapters/src/chat/telegram/adapter.ts. That portion of the original PR has been dropped on rebase.

UX Journey

Before

  User                 Server                   Web UI (Settings)
  ────                 ──────                   ─────────────────
  opens Settings ────▶ GET /api/health
                       returns { adapter } ────▶ shows only Web
                                                 others hardcoded 'Not configured' ❌

After

  User                 Server                   Web UI (Settings)
  ────                 ──────                   ─────────────────
  opens Settings ────▶ GET /api/health
                       returns { adapter,
                              *activePlatforms*: ['Web','Slack',...] } ────▶ reflects real state ✅

Architecture Diagram

Before

startServer
├─ adapter.start() (each platform)
└─ (log-only) activePlatforms computed at end       registerApiRoutes
                                                    └─ GET /api/health → { adapter }

After

startServer
├─ [+] const activePlatforms: string[] = ['Web']
├─ adapter.start(); [+] activePlatforms.push(name)   registerApiRoutes(..., [+] activePlatforms)
└─ log activePlatforms (reuses the live array)       └─ GET /api/health → { adapter, [+] activePlatforms }

Connection inventory:

From To Status Notes
startServer registerApiRoutes(activePlatforms) modified Live array reference
each adapter start() activePlatforms.push(...) new Populated as they start
GET /api/health HealthResponse.activePlatforms new Optional field, defaults to ['Web']
SettingsPage health.activePlatforms modified Reads real data (was hardcoded false)

Label Snapshot

  • Risk: risk: low
  • Size: size: S
  • Scope: server, web
  • Module: server:index, server:routes/api, web:SettingsPage

Change Metadata

  • Change type: fix
  • Primary scope: multi (server + web)

Linked Issue

Validation Evidence (required)

bun run type-check              # ✅ 10 packages
bun run lint                    # ✅ 0 errors, 0 warnings
bun --filter @archon/server test  # ✅ 46 tests pass

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

activePlatforms exposes only platform names (e.g. "Slack") — no tokens or config values.

Compatibility / Migration

  • Backward compatible? YesactivePlatforms is an optional field; clients that ignore it see no change.
  • Config/env changes? No
  • Database migration needed? No

Human Verification (required)

  • Verified scenarios: type-check / lint / @archon/server test suite pass locally.
  • Edge cases checked: Telegram starts after registerApiRoutes runs — the live array reference ensures /api/health reflects its state once it pushes. getHealth() without platforms configured returns ['Web'] as before.
  • What was not verified: Live browser check of the Settings page rendering against a running server.

Side Effects / Blast Radius (required)

  • Affected subsystems: /api/health, Settings page.
  • Potential unintended effects: None. The array is read-only from the handler's perspective; a copy is emitted each response.
  • Guardrails: Existing adapter-start logs (*.bot_started, github_webhook_registered, etc.) remain the source of truth for startup observability.

Rollback Plan (required)

  • Fast rollback: git revert <commit> — single commit, 4 files, contained.
  • Feature flags or config toggles: N/A.
  • Observable failure symptoms: Settings page returns to showing everything as "Not configured".

Risks and Mitigations

  • Risk: Telegram adapter fails to start but earlier code path already pushes 'Telegram' to the array.
    • Mitigation: Push is inside the try block after await telegramAdapter.start(); failure skips the push (matches pre-existing telegram = null handling).

Rebased from @liorfranko's original PR onto current dev. The adapter changes were dropped because dev migrated from telegraf to grammY and already resolves the bot.launch() never-resolves issue via the onStart callback — so that fix is no longer needed. The server + web portions remain and are credited to @liorfranko.

Summary by CodeRabbit

  • New Features

    • Settings panel now displays real-time connection status for all active platforms, including Gitea and GitLab.
    • Health check endpoint reports which platforms are currently active.
  • Bug Fixes

    • Platform connection status now reflects actual running platforms instead of hardcoded defaults.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d393dad-6bdf-4796-88a1-cb103b926846

📥 Commits

Reviewing files that changed from the base of the PR and between 6a816a9 and c31e481.

📒 Files selected for processing (4)
  • packages/server/src/index.ts
  • packages/server/src/routes/api.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web/src/routes/SettingsPage.tsx
  • packages/server/src/routes/api.ts
  • packages/server/src/index.ts

📝 Walkthrough

Walkthrough

A mutable activePlatforms array is initialized at server startup and populated as each adapter successfully starts. The shared array is passed to registerApiRoutes where it's exposed via the /api/health endpoint. The web UI consumes this data to display real-time platform connection status.

Changes

Cohort / File(s) Summary
Server-side platform tracking
packages/server/src/index.ts, packages/server/src/routes/api.ts
Introduces activePlatforms array initialized to ['Web'], populated incrementally as adapters start, and passed to API routes. /api/health endpoint extended with optional activePlatforms field in response schema and handler logic.
Web platform status integration
packages/web/src/lib/api.ts, packages/web/src/routes/SettingsPage.tsx
HealthResponse type updated with optional activePlatforms field. PlatformConnectionsSection refactored to determine platform connection status by checking presence in activePlatforms set, expanding rendered platform list to include Gitea and GitLab.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Poem

🐰 The platforms gather, one by one,
Through startup dance till all are done!
In health responses, bright and clear,
The web now knows which platforms appear! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: showing real platform connection status in the Settings page, referencing the closed issue.
Description check ✅ Passed The description comprehensively covers all required template sections including summary, UX journey, architecture diagram, validation evidence, security impact, compatibility, human verification, side effects, and rollback plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/adapters/src/chat/telegram/adapter.ts`:
- Around line 196-217: The promise currently resolves inside the telegraf
onLaunch callback (this.bot.launch(..., () => { ... resolve() })), but onLaunch
fires before polling/getUpdates begin so 409 errors surface after resolve and
bypass the retry try/catch; change the logic so the outer promise does not
resolve on onLaunch but only after polling is actually started or the launch()
promise resolves/rejects: e.g., remove resolving from the onLaunch callback and
instead await/attach to the Promise returned by this.bot.launch(), or listen for
an event that confirms startPolling (or handle 'polling_error') and resolve only
when polling is confirmed, ensuring .catch on the launch promise can reject and
trigger the existing retry loop (relate to this.bot.launch, onLaunch,
startPolling, getUpdates).

In `@packages/server/src/index.ts`:
- Around line 490-494: activePlatforms is initialized once but not updated when
other adapters finish starting, causing /api/health to report stale data; after
each adapter successfully starts (e.g., when calling start() on
Telegram/Discord/etc.), immediately push that adapter's platform identifier into
the existing activePlatforms array (use the same array passed into
registerApiRoutes) — for example, in the promise resolution or async/await right
after adapter.start() do activePlatforms.push('Telegram') or
activePlatforms.push(adapter.platformName) so the health endpoint reflects
adapters as soon as they are up (ensure this change is applied in each adapter
startup location that currently only starts the adapter without mutating
activePlatforms).

In `@packages/web/src/routes/SettingsPage.tsx`:
- Around line 615-619: The settings UI is missing the server-reported platforms
Gitea and GitLab, so update the static platforms list in SettingsPage (the array
that currently contains { name: 'Web', connected: active.has('Web') }, ... {
name: 'GitHub', connected: active.has('GitHub') }) to include { name: 'Gitea',
connected: active.has('Gitea') } and { name: 'GitLab', connected:
active.has('GitLab') }, or replace the hardcoded list with a dynamic map derived
from the server-provided active set (using the existing active variable and
active.has checks) so the UI reflects whatever platforms the server reports.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fe1e94b-bdd9-4deb-a021-a4c4d40092e2

📥 Commits

Reviewing files that changed from the base of the PR and between e8334b3 and 50be822.

📒 Files selected for processing (5)
  • packages/adapters/src/chat/telegram/adapter.ts
  • packages/server/src/index.ts
  • packages/server/src/routes/api.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx

Comment thread packages/adapters/src/chat/telegram/adapter.ts Outdated
Comment thread packages/server/src/index.ts Outdated
Comment thread packages/web/src/routes/SettingsPage.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/adapters/src/chat/telegram/adapter.test.ts (1)

247-258: Add one regression test for a 409 that happens after onLaunch.

These cases still only exercise immediate launch() rejection. The new production path is the opposite ordering: onLaunch fires, then polling startup fails during the grace window. Without that case, the core race this PR fixes is still unproven.

🧪 Suggested test shape
+    test('should retry when polling fails after onLaunch but before grace settles', async () => {
+      const adapter = new TelegramAdapter('fake-token-for-testing');
+      const conflictError = new Error('409: Conflict: terminated by other getUpdates request');
+      let callCount = 0;
+
+      const mockLaunch = mock((_config: unknown, onLaunch?: () => void): Promise<void> => {
+        callCount++;
+        if (callCount === 1) {
+          onLaunch?.();
+          return new Promise((_, reject) => setTimeout(() => reject(conflictError), 0));
+        }
+        onLaunch?.();
+        return new Promise(() => {});
+      });
+
+      (adapter.getBot() as unknown as { launch: typeof mockLaunch }).launch = mockLaunch;
+
+      await adapter.start({ retryDelayMs: 0, pollingGraceMs: 10 });
+      expect(mockLaunch).toHaveBeenCalledTimes(2);
+    });

As per coding guidelines "Prefer reproducible commands in CI-sensitive paths; keep tests deterministic without flaky timing or network dependence; ensure local validation commands map to CI expectations."

Also applies to: 286-296, 305-310

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adapters/src/chat/telegram/adapter.test.ts` around lines 247 - 258,
Add a deterministic regression test that simulates a 409 Conflict occurring
after the telegraf onLaunch callback (to exercise the race where polling fails
during the grace window) by updating the mocked launch used in the existing
test: have mockLaunch call onLaunch() immediately, then simulate a polling-start
failure that rejects with new Error('409: Conflict: terminated by other
getUpdates request') at a controlled time (within the pollingGraceMs window)
instead of rejecting immediately; use adapter.getBot().launch = mockLaunch and
call adapter.start({ retryDelayMs: 0, pollingGraceMs: 0 or small value }) and
assert the adapter handles the post-onLaunch 409 the same as the pre-onLaunch
case (retries/continues) so the race is exercised deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/routes/SettingsPage.tsx`:
- Around line 608-623: The platforms list currently marks integrations as not
connected whenever activePlatforms is undefined; change the connected value
computation in PlatformConnectionsSection so it is tri-state
(true/false/undefined) by setting connected: activePlatforms ? active.has('Web')
: undefined (and likewise for each platform name) instead of active.has(...),
and update the component rendering logic that consumes platforms (the code
reading platforms[].connected) to display an "Unknown"/"Loading" state when
connected is undefined rather than showing "Not configured".

---

Nitpick comments:
In `@packages/adapters/src/chat/telegram/adapter.test.ts`:
- Around line 247-258: Add a deterministic regression test that simulates a 409
Conflict occurring after the telegraf onLaunch callback (to exercise the race
where polling fails during the grace window) by updating the mocked launch used
in the existing test: have mockLaunch call onLaunch() immediately, then simulate
a polling-start failure that rejects with new Error('409: Conflict: terminated
by other getUpdates request') at a controlled time (within the pollingGraceMs
window) instead of rejecting immediately; use adapter.getBot().launch =
mockLaunch and call adapter.start({ retryDelayMs: 0, pollingGraceMs: 0 or small
value }) and assert the adapter handles the post-onLaunch 409 the same as the
pre-onLaunch case (retries/continues) so the race is exercised
deterministically.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bfa8b72-4bd9-4f89-bf0c-c6d27687bf9f

📥 Commits

Reviewing files that changed from the base of the PR and between 50be822 and 6a816a9.

📒 Files selected for processing (4)
  • packages/adapters/src/chat/telegram/adapter.test.ts
  • packages/adapters/src/chat/telegram/adapter.ts
  • packages/server/src/index.ts
  • packages/web/src/routes/SettingsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/index.ts

Comment on lines 608 to 623
function PlatformConnectionsSection({
adapter,
activePlatforms,
}: {
adapter: string | undefined;
activePlatforms: string[] | undefined;
}): React.ReactElement {
const active = new Set(activePlatforms ?? []);
const platforms = [
{ name: 'Web', connected: adapter === 'web' },
{ name: 'Slack', connected: false },
{ name: 'Telegram', connected: false },
{ name: 'Discord', connected: false },
{ name: 'GitHub', connected: false },
{ name: 'Web', connected: active.has('Web') },
{ name: 'Slack', connected: active.has('Slack') },
{ name: 'Telegram', connected: active.has('Telegram') },
{ name: 'Discord', connected: active.has('Discord') },
{ name: 'GitHub', connected: active.has('GitHub') },
{ name: 'Gitea', connected: active.has('Gitea') },
{ name: 'GitLab', connected: active.has('GitLab') },
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid showing “Not configured” before health data exists.

When activePlatforms is still undefined (initial load or health fetch failure), this renders every integration as Not configured. That reintroduces a false status during the exact window this PR is trying to fix. Render an Unknown/Loading state until the health payload arrives.

💡 Suggested tweak
 function PlatformConnectionsSection({
   activePlatforms,
 }: {
   activePlatforms: string[] | undefined;
 }): React.ReactElement {
+  const hasHealthData = activePlatforms !== undefined;
   const active = new Set(activePlatforms ?? []);
   const platforms = [
     { name: 'Web', connected: active.has('Web') },
     { name: 'Slack', connected: active.has('Slack') },
     { name: 'Telegram', connected: active.has('Telegram') },
@@
           {platforms.map(p => (
             <div key={p.name} className="flex items-center justify-between text-sm">
               <span>{p.name}</span>
-              <Badge variant={p.connected ? 'default' : 'secondary'}>
-                {p.connected ? 'Connected' : 'Not configured'}
+              <Badge variant={hasHealthData && p.connected ? 'default' : 'secondary'}>
+                {!hasHealthData ? 'Unknown' : p.connected ? 'Connected' : 'Not configured'}
               </Badge>
             </div>
           ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/SettingsPage.tsx` around lines 608 - 623, The
platforms list currently marks integrations as not connected whenever
activePlatforms is undefined; change the connected value computation in
PlatformConnectionsSection so it is tri-state (true/false/undefined) by setting
connected: activePlatforms ? active.has('Web') : undefined (and likewise for
each platform name) instead of active.has(...), and update the component
rendering logic that consumes platforms (the code reading platforms[].connected)
to display an "Unknown"/"Loading" state when connected is undefined rather than
showing "Not configured".

@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 17, 2026

This PR appears to fully address #1031. Consider adding Closes #1031 to the PR body so the issue auto-closes on merge.

The Settings page's Platform Connections section hardcoded every platform
except Web to 'Not configured', so users couldn't tell whether their Slack/
Telegram/Discord/GitHub/Gitea/GitLab adapters had actually started.

- Server: /api/health now returns an activePlatforms array populated live
  as each adapter's start() resolves. Passed into registerApiRoutes so the
  reference stays mutable — Telegram starts after the HTTP listener is
  already accepting requests, so a snapshot would miss it.
- Web: SettingsPage.PlatformConnectionsSection now reads activePlatforms
  from /api/health and looks each platform up in a Set. Also adds Gitea
  and GitLab to the list (they already ship as adapters).

Closes coleam00#1031
@Wirasm Wirasm force-pushed the fix/platform-connections-status branch from 6a816a9 to c31e481 Compare April 20, 2026 11:04
@Wirasm Wirasm changed the title fix(web,server,adapters): show real platform connection status fix(web,server): show real platform connection status in Settings (closes #1031) Apr 20, 2026
@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 20, 2026

Hi @liorfranko — thanks for this! Rebased onto current dev for you. The adapter.ts + adapter.test.ts Telegram changes were dropped on rebase because dev migrated from telegraf to grammY (landed after you opened this PR) and already solves the bot.launch() never-resolves issue via grammY's onStart callback — your fix was exactly right at the time, just no longer needed. The server + web portions (the actual #1031 fix) stay, credited to you.

Also added Closes #1031 to the body so it auto-closes on merge.

@Wirasm Wirasm merged commit 08de8ee into coleam00:dev Apr 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings page shows all platform adapters as 'Not configured' even when running

2 participants