Skip to content

Conversation

coderanik
Copy link

@coderanik coderanik commented Oct 15, 2025

PR Title

Improve app startup reliability and fix lag when switching pages; add API key caching

Summary

  • Fixed Docker startup issues (port conflicts, healthcheck, env) and ensured backend connects to Postgres reliably.
  • Eliminated laggy data reloads on page switches by keeping page views mounted and toggling visibility.
  • Switched API key page to React Query for caching and smooth UX.

Changes

  • Backend/docker

    • Updated maxun/docker-compose.yml:
      • Changed Postgres healthcheck to respect configured user:
        • From: pg_isready -U postgres
        • To: pg_isready -U ${DB_USER}
      • Decoupled internal vs host DB ports:
        • From: "${DB_PORT:-5432}:${DB_PORT:-5432}"
        • To: "${DB_HOST_PORT:-5433}:5432" (container stays on 5432; host exposes 5433)
    • Updated environment:
      • Added DB_HOST=postgres to .env so services connect via the Docker network name
      • Ensured DB_PORT=5432 for internal connections
      • Added DB_HOST_PORT=5433 to map host port without collisions
  • Frontend performance (page switch lag)

    • Edited maxun/src/pages/MainPage.tsx:
      • Kept subviews mounted (robots, runs, proxy, apikey) and switched visibility with inline display: none/block instead of unmounting/mounting
      • Memoized the subviews with useMemo to avoid unnecessary re-renders
      • Effect: avoids refetch and component remount churn when switching tabs, making navigation instant
  • Frontend data fetching (API key)

    • Refactored maxun/src/components/api/ApiKey.tsx:
      • Fetch: replaced manual useEffect + axios with React Query useQuery keyed as ['api-key'] with staleTime 10 min
      • Mutations:
        • Generate: React Query useMutation, updates cache via queryClient.setQueryData(['api-key'], newKey)
        • Delete: React Query useMutation, clears cache via queryClient.setQueryData(['api-key'], null)
      • Preserved existing notifications and with-credentials behavior; UI buttons now disabled while mutation is in-flight
      • Effect: reduces redundant requests and improves responsiveness on revisit

Rationale

  • Postgres connection failures were caused by:
    • Healthcheck using hardcoded postgres user while the service used DB_USER
    • Host port 5432 was already in use; mapping to 5433 on host resolves conflicts while keeping 5432 inside Docker
    • Missing DB_HOST=postgres caused services to try localhost instead of the Docker network host
  • Page switch lag came from remounting components that refetched on mount. Keeping views mounted eliminates the reload cost.
  • API key view often refetched unnecessarily; React Query adds caching, retries, and consistent loading states.

Files Touched

  • maxun/docker-compose.yml
    • Changed healthcheck user
    • Changed Postgres port mapping to ${DB_HOST_PORT:-5433}:5432
  • .env (created/updated)
    • Added DB_HOST=postgres
    • Set DB_PORT=5432
    • Added DB_HOST_PORT=5433
  • maxun/src/pages/MainPage.tsx
    • Kept subviews mounted; added useMemo for robotsView, runsView, proxyView, apiKeyView
    • Replaced dynamic rendering with display: none/block wrappers
  • maxun/src/components/api/ApiKey.tsx
    • Switched to @tanstack/react-query for fetching/mutations and cache updates

How to Test

  • Environment and Docker

    • Ensure .env contains:
      • DB_HOST=postgres
      • DB_PORT=5432
      • DB_HOST_PORT=5433
    • Start stack:
      • cd /Users/anik/Code/OPEN-SOURCE/maxun && docker-compose up -d
    • Verify containers:
      • docker-compose ps (backend on 8080, frontend on 5173, Postgres mapped to 5433)
    • Database connectivity from host:
      • psql "postgresql://<DB_USER>:<DB_PASSWORD>@localhost:5433/<DB_NAME>"
  • Frontend performance

    • Visit http://localhost:5173
    • Switch between Robots, Runs, Proxy, API Key in the side menu repeatedly
    • Expect instant view switches without spinners/lag or repeated network calls (devtools Network tab)
  • API Key page

    • Navigate to API Key
    • If no key exists, click Generate; key should appear and stay cached across tab switches
    • Click Delete; key disappears; no extraneous reloads
    • Reload page; React Query should re-hydrate after fetch without flicker

Compatibility/Notes

  • No schema changes
  • Port exposure changed on host to 5433; internal services remain on 5432
  • React Query provider already present (GlobalInfoProvider), so no provider changes required
  • Button visuals unchanged

Summary by CodeRabbit

  • Refactor

    • Streamlined API key management with improved data fetching, caching, and built‑in loading states; generate/delete actions now disable buttons during processing and update immediately.
    • Optimized main page to keep tab views mounted, preserving state, preventing unnecessary refetches, and making tab switching smoother.
  • Chores

    • Removed the example environment configuration file.
    • Adjusted database service defaults in the container setup, including a new host port mapping and a health check that respects the configured user.

Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

Removes all content from ENVEXAMPLE, updates postgres port mapping and healthcheck in docker-compose.yml, refactors ApiKey component to React Query for fetching/mutations, and restructures MainPage to memoize subviews to avoid remounts during tab switches.

Changes

Cohort / File(s) Summary
Environment template cleanup
ENVEXAMPLE
Deleted all example environment variables including app mode, secrets, DB, MinIO, Redis, URLs/ports, OAuth, Airtable, and telemetry.
Docker compose adjustments
docker-compose.yml
Changed postgres port mapping to ${DB_HOST_PORT:-5433}:5432; updated healthcheck to use pg_isready -U ${DB_USER}.
API key React Query refactor
src/components/api/ApiKey.tsx
Replaced local state/effect with useQuery for API key fetch and useMutation for generate/delete; updates cache via setQueryData; buttons disabled during pending mutations; loading now driven by isLoading.
Main page view memoization
src/pages/MainPage.tsx
Removed switch-based subcomponent; added useMemo for robots/runs/proxy/apiKey views; renders conditionally to keep subviews mounted across tab changes; no public API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant V as ApiKeyManager (UI)
  participant Q as React Query
  participant S as Backend API

  U->>V: Open API Key page
  V->>Q: useQuery("apiKey", fetch)
  Q->>S: GET /api/key
  S-->>Q: { apiKey }
  Q-->>V: data, isLoading=false

  U->>V: Click Generate
  V->>Q: useMutation(generate)
  Q->>S: POST /api/key
  S-->>Q: { apiKey }
  Q-->>Q: setQueryData("apiKey", newKey)
  Q-->>V: onSuccess (button re-enabled)

  U->>V: Click Delete
  V->>Q: useMutation(delete)
  Q->>S: DELETE /api/key
  S-->>Q: 204
  Q-->>Q: setQueryData("apiKey", null)
  Q-->>V: onSuccess (button re-enabled)
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant M as MainPage
  participant R as Memoized Views
  note over M,R: Views are memoized via useMemo and remain mounted

  U->>M: Switch tab
  M->>R: Conditionally show memoized view
  R-->>U: Render without remount/refetch
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Type: Chore

Poem

I nibbled configs, left the fields clean bare,
Hopped ports to 5433 with careful care.
Query burrow caches keys with glee,
Memoized meadows keep views carefree.
Thump-thump: no remount fright—
Just swift carrots, crisp and right. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title highlights the UI lag fix related to data loading between page switches, which is a valid part of the changeset, but it does not convey the Docker startup adjustments or the API key caching enhancements also included in this PR. It is specific enough about the UI improvement yet omits other significant updates. Despite this limitation, it accurately reflects one of the core objectives.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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 (5)
docker-compose.yml (1)

10-16: Ports/healthcheck LGTM; add start_period and wait-for-healthy for reliability.

Compose will resolve ${DB_USER} at parse time—good. To avoid flapping on cold starts and ensure backend waits for DB readiness, add a start period and depends_on conditions.

Apply:

 services:
   postgres:
     ...
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
-      interval: 10s
-      timeout: 5s
-      retries: 5
+      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 30s

   backend:
     ...
-    depends_on:
-      - postgres
-      - minio
+    depends_on:
+      postgres:
+        condition: service_healthy
+      minio:
+        condition: service_started

   frontend:
     ...
-    depends_on:
-      - backend
+    depends_on:
+      backend:
+        condition: service_started
src/components/api/ApiKey.tsx (1)

41-49: Handle fetch errors explicitly (avoid showing “no key” on network errors).

When the query errors, apiKey is undefined and the UI shows the “no key” message. Show an error state or toast.

Example:

-const { data: apiKey, isLoading } = useQuery({
+const { data: apiKey, isLoading, isError } = useQuery({
   ...
 });
 
-if (isLoading) {
+if (isLoading) {
   ...
 }
+if (isError) {
+  return <Container><Typography color="error">{t('apikey.notifications.fetch_error')}</Typography></Container>;
+}
src/pages/MainPage.tsx (3)

306-314: Stabilize abortRunHandler with useCallback to maximize memoization gains.

runsView re-memoizes every render because abortRunHandler isn’t stable.

Wrap it:

-const abortRunHandler = (runId: string, robotName: string, browserId: string) => {
+const abortRunHandler = useCallback((runId: string, robotName: string, browserId: string) => {
   ...
-}
+}, [t, notify, setRerenderRuns, invalidateRuns]);

Then keep runsView deps unchanged. Optionally also wrap setRecordingInfo similarly to stabilize robotsView.


323-334: Improve hidden view accessibility.

Add hidden/aria-hidden so offscreen content isn’t read by screen readers.

-<div style={{ display: content === 'robots' ? 'block' : 'none', width: '100%' }}>
+<div style={{ display: content === 'robots' ? 'block' : 'none', width: '100%' }}
+     hidden={content !== 'robots'}
+     aria-hidden={content !== 'robots'}>
   {robotsView}
 </div>
-<div style={{ display: content === 'runs' ? 'block' : 'none', width: '100%' }}>
+<div style={{ display: content === 'runs' ? 'block' : 'none', width: '100%' }}
+     hidden={content !== 'runs'}
+     aria-hidden={content !== 'runs'}>
   {runsView}
 </div>
-<div style={{ display: content === 'proxy' ? 'block' : 'none', width: '100%' }}>
+<div style={{ display: content === 'proxy' ? 'block' : 'none', width: '100%' }}
+     hidden={content !== 'proxy'}
+     aria-hidden={content !== 'proxy'}>
   {proxyView}
 </div>
-<div style={{ display: content === 'apikey' ? 'block' : 'none', width: '100%' }}>
+<div style={{ display: content === 'apikey' ? 'block' : 'none', width: '100%' }}
+     hidden={content !== 'apikey'}
+     aria-hidden={content !== 'apikey'}>
   {apiKeyView}
 </div>

296-305: Keep-mounted views: consider pausing heavy work when hidden.

If these subviews poll or open sockets, pause them when not visible (e.g., pass a visible prop to gate queries with enabled, or pause subscriptions).

Also applies to: 306-314, 315-317

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af610c4 and 2278bff.

📒 Files selected for processing (4)
  • ENVEXAMPLE (0 hunks)
  • docker-compose.yml (1 hunks)
  • src/components/api/ApiKey.tsx (6 hunks)
  • src/pages/MainPage.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • ENVEXAMPLE
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/MainPage.tsx (3)
src/components/robot/Recordings.tsx (1)
  • Recordings (24-157)
src/components/run/Runs.tsx (1)
  • Runs (12-27)
src/components/dashboard/MainMenu.tsx (1)
  • MainMenu (16-164)
🔇 Additional comments (1)
src/components/api/ApiKey.tsx (1)

45-46: Confirm axios credential config.

Ensure axios adds withCredentials: true (global instance or per call), since auth likely relies on cookies.

Do you have a central axios setup enabling withCredentials? If not, I can propose a small axios instance wrapper.

Also applies to: 54-54, 69-69

Comment on lines +41 to +49
// Fetch API key with React Query
const { data: apiKey, isLoading } = useQuery({
queryKey: ['api-key'],
queryFn: async () => {
const { data } = await axios.get(`${apiUrl}/auth/api-key`);
return data.api_key as string | null;
},
staleTime: 10 * 60 * 1000, // 10 minutes
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Scope cache by user to prevent API key leakage across logins.

Using a global ['api-key'] with 10m staleTime can show a previous user’s API key after sign‑out/in in the same tab. Scope by user.id (and gate with enabled) and update mutations accordingly.

Apply:

-import React, { useState } from 'react';
+import React, { useState, useContext } from 'react';
 ...
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { AuthContext } from '../../context/auth';
 ...
 const ApiKeyManager = () => {
   const { t } = useTranslation();
   const [apiKeyName, setApiKeyName] = useState<string>(t('apikey.default_name'));
   const [showKey, setShowKey] = useState<boolean>(false);
   const [copySuccess, setCopySuccess] = useState<boolean>(false);
   const { notify } = useGlobalInfoStore();
   const queryClient = useQueryClient();
+  const { state } = useContext(AuthContext);
+  const { user } = state || {};
 
   // Fetch API key with React Query
-  const { data: apiKey, isLoading } = useQuery({
-    queryKey: ['api-key'],
+  const { data: apiKey, isLoading } = useQuery({
+    queryKey: ['api-key', user?.id],
     queryFn: async () => {
       const { data } = await axios.get(`${apiUrl}/auth/api-key`);
       return data.api_key as string | null;
     },
     staleTime: 10 * 60 * 1000, // 10 minutes
+    enabled: !!user?.id,
   });

Also update mutations:

-  const { mutate: generateApiKey, isPending: isGenerating } = useMutation({
+  const { mutate: generateApiKey, isPending: isGenerating } = useMutation({
     mutationFn: async () => {
       const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`);
       return data.api_key as string;
     },
-    onSuccess: (newKey) => {
-      queryClient.setQueryData(['api-key'], newKey);
+    onSuccess: (newKey) => {
+      queryClient.setQueryData(['api-key', user?.id], newKey);
       notify('success', t('apikey.notifications.generate_success'));
     },
-  const { mutate: deleteApiKey, isPending: isDeleting } = useMutation({
+  const { mutate: deleteApiKey, isPending: isDeleting } = useMutation({
     mutationFn: async () => {
       await axios.delete(`${apiUrl}/auth/delete-api-key`);
     },
     onSuccess: () => {
-      queryClient.setQueryData(['api-key'], null);
+      queryClient.setQueryData(['api-key', user?.id], null);
       notify('success', t('apikey.notifications.delete_success'));
     },
📝 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
// Fetch API key with React Query
const { data: apiKey, isLoading } = useQuery({
queryKey: ['api-key'],
queryFn: async () => {
const { data } = await axios.get(`${apiUrl}/auth/api-key`);
return data.api_key as string | null;
},
staleTime: 10 * 60 * 1000, // 10 minutes
});
// Fetch API key with React Query
const { data: apiKey, isLoading } = useQuery({
queryKey: ['api-key', user?.id],
queryFn: async () => {
const { data } = await axios.get(`${apiUrl}/auth/api-key`);
return data.api_key as string | null;
},
staleTime: 10 * 60 * 1000, // 10 minutes
enabled: !!user?.id,
});
🤖 Prompt for AI Agents
In src/components/api/ApiKey.tsx around lines 41 to 49, the query uses a global
['api-key'] which can leak one user's API key to another after sign-out/in;
change the queryKey to include the current user's id (e.g. ['api-key', user.id])
and add enabled: !!user?.id so the query only runs when a user is present; also
update any mutations that create/delete/regenerate the API key to invalidate or
update the same scoped key (['api-key', user.id]) instead of the global key so
cache updates are per-user and no stale key is shown across logins.

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.

1 participant