Skip to content

Conversation

@RohitR311
Copy link
Collaborator

@RohitR311 RohitR311 commented Aug 29, 2025

Closes: #747

image image

Summary by CodeRabbit

  • New Features

    • Loading indicators added for Recordings and Runs; views now reliably fetch data on mount.
    • Empty-state placeholders adapt to debounced search input and show contextual messages when no results.
  • Style

    • Sort icons reveal on header hover; pagination shown appropriately per-group and overall in Runs.
  • Localization

    • Added EN/DE/ES/JA/TR/ZH translations for new placeholders and search hints.

@coderabbitai
Copy link

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds isLoading states and spinner-driven loading UI to RecordingsTable and RunsTable; introduces debouncedSearchTerm for search-aware empty-state placeholders; always-fetch on mount; preserves existing table/accordion rendering and pagination for non-empty results; adds localized placeholder keys across multiple locale files and a minor RunsTable header hover tweak.

Changes

Cohort / File(s) Summary
Recordings: UI + loading
src/components/robot/RecordingsTable.tsx
Add isLoading + CircularProgress; always fetch on mount; use useDebounce debouncedSearchTerm; three-way rendering: spinner, search-aware centered placeholder, or existing table + pagination.
Runs: UI + loading + header hover
src/components/run/RunsTable.tsx
Add isLoading + CircularProgress; always fetch on mount; three-way rendering: spinner, search-aware centered placeholder, or per-robot accordions with nested tables and pagination; header label wrapped to reveal sort icon on hover.
i18n: placeholders added
public/locales/*.json
public/locales/en.json, de.json, es.json, ja.json, tr.json, zh.json
Add recordingtable.search_criteria, recordingtable.placeholder, and runstable.placeholder localized keys across locales; es.json also relocates interpretation_log entries (structure change).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SearchInput as Search/Input (debounced)
  participant RecordingsTable
  participant RunsTable
  Note right of SearchInput: useDebounce -> debouncedSearchTerm
  User->>SearchInput: type query
  SearchInput-->>RecordingsTable: debouncedSearchTerm
  SearchInput-->>RunsTable: debouncedSearchTerm
  alt Loading
    RecordingsTable-->>User: show CircularProgress
    RunsTable-->>User: show CircularProgress
  else Not loading
    alt No results (recordings)
      RecordingsTable-->>User: show centered placeholder (search-aware)
    else Results exist (recordings)
      RecordingsTable-->>User: render table + pagination
    end
    alt No results (runs)
      RunsTable-->>User: show centered placeholder (search-aware)
    else Results exist (runs)
      RunsTable-->>User: render accordions -> nested tables -> per-robot pagination + outer pagination
      User->>RunsTable: hover header
      RunsTable-->>User: reveal sort icon
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Add placeholder for empty runs & robots UI (#747)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Header hover/visibility tweak for sort icon (src/components/run/RunsTable.tsx) Minor visual/interaction change not required by placeholder objective (#747).
Relocation of interpretation_log structure (public/locales/es.json) Translation key restructuring unrelated to adding empty-state placeholders in (#747).

Possibly related issues

Possibly related PRs

Suggested labels

Type: Feature, Scope: UI/UX, Scope: i18n Translations

Suggested reviewers

  • amhsirak

Poem

I hop across the codebase, small and spry,
A spinner blinks while searches say goodbye.
Placeholders bloom when rows are few,
Debounced whispers bring results anew.
I thump in joy when tables come into view. 🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/run/RunsTable.tsx (1)

198-211: Debounce is ineffective; a new debounced fn is recreated per keystroke.

This schedules multiple timeouts instead of coalescing them.

-  const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
-    const debouncedSetSearch = debouncedSearch((value: string) => {
-      setSearchTerm(value);
-      setAccordionPage(0);
-      setPaginationStates(prev => {
-        const reset = Object.keys(prev).reduce((acc, robotId) => ({
-          ...acc,
-          [robotId]: { ...prev[robotId], page: 0 }
-        }), {});
-        return reset;
-      });
-    }, 300);
-    debouncedSetSearch(event.target.value);
-  }, [debouncedSearch]);
+  const debouncedSetSearch = useMemo(() => debouncedSearch((value: string) => {
+    setSearchTerm(value);
+    setAccordionPage(0);
+    setPaginationStates(prev => {
+      const reset = Object.keys(prev).reduce((acc, robotId) => ({
+        ...acc,
+        [robotId]: { ...prev[robotId], page: 0 }
+      }), {});
+      return reset;
+    });
+  }, 300), [debouncedSearch]);
+
+  const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
+    debouncedSetSearch(event.target.value);
+  }, [debouncedSetSearch]);

Optionally make the TextField controlled: add value={searchTerm}.

🧹 Nitpick comments (2)
src/components/robot/RecordingsTable.tsx (2)

523-528: Match empty-state spacing with non-empty table spacing.

Add top margin so the placeholder aligns visually with the table path (which has marginTop: 15px).

         <Box
           display="flex"
           flexDirection="column"
           alignItems="center"
           justifyContent="center"
           sx={{ 
             minHeight: 300, 
             textAlign: 'center',
-            color: 'text.secondary' 
+            color: 'text.secondary',
+            marginTop: '15px'
           }}
         >

529-538: Optional: Add a CTA to create a new recording directly from the empty state.

Improves discoverability and reduces clicks.

           <Typography variant="body2" color="text.secondary">
             {debouncedSearchTerm 
               ? t('recordingtable.search_criteria')
               : t('recordingtable.placeholder.body')
             }
           </Typography>
+          <Button variant="contained" sx={{ mt: 2 }} onClick={handleNewRecording}>
+            {t('recordingtable.new')}
+          </Button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4b68efb and bdafb18.

📒 Files selected for processing (2)
  • src/components/robot/RecordingsTable.tsx (1 hunks)
  • src/components/run/RunsTable.tsx (1 hunks)
🔇 Additional comments (2)
src/components/robot/RecordingsTable.tsx (1)

516-578: Empty-state handling is clear and consistent.

Conditional render + removal of pagination when empty looks good.

src/components/run/RunsTable.tsx (1)

381-403: Empty-state rendering reads well.

Clear messaging with search-aware text is good.

Comment on lines 381 to 403
{Object.keys(groupedRows).length === 0 ? (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
sx={{
minHeight: 300,
textAlign: 'center',
color: 'text.secondary'
}}
>
<Typography variant="h6" gutterBottom>
{searchTerm ? t('runstable.placeholder.search') : t('runstable.placeholder.title')}
</Typography>
<Typography variant="body2" color="text.secondary">
{searchTerm
? t('recordingtable.search_criteria')
: t('runstable.placeholder.body')
}
</Typography>
</Box>
) : (
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify i18n keys exist for new runstable strings.

Ensure these keys exist: runstable.placeholder.title/body/search, runstable.search_criteria, runstable.sort_tooltip.

Also applies to: 424-468, 492-500


🏁 Script executed:

#!/bin/bash
# Check runstable translation keys
rg -n --glob '!**/node_modules/**' \
  -e '"runstable\.placeholder\.title"' \
  -e '"runstable\.placeholder\.body"' \
  -e '"runstable\.placeholder\.search"' \
  -e '"runstable\.search_criteria"' \
  -e '"runstable\.sort_tooltip"' \
  -- **/*.{json,ts,tsx}

Length of output: 368


🏁 Script executed:

#!/bin/bash
# List JSON translation files in src
find src -type f -name '*.json' ! -path '*/node_modules/*'
# Show first 200 lines of src/i18n.ts to locate translation resource loading
echo "----- src/i18n.ts -----"
head -n 200 src/i18n.ts

Length of output: 695


🏁 Script executed:

#!/bin/bash
# List all JSON files in the repo (excluding node_modules)
find . -type f -name '*.json' ! -path '*/node_modules/*'
# Find any 'locales' directories in the repo
find . -type d -name 'locales' ! -path '*/node_modules/*'
# If a public/locales directory exists, list its contents
if [ -d "./public/locales" ]; then
  echo "---- public/locales ----"
  ls -1 ./public/locales
fi

Length of output: 639


🏁 Script executed:

#!/bin/bash
# Verify translation keys in locale JSON files
python3 - << 'EOF'
import json, os
languages = ['en','es','ja','zh','de','tr']
keys = [
    'runstable.placeholder.title',
    'runstable.placeholder.body',
    'runstable.placeholder.search',
    'runstable.search_criteria',
    'runstable.sort_tooltip'
]
for lang in languages:
    path = os.path.join('public','locales', f'{lang}.json')
    try:
        data = json.load(open(path, 'r', encoding='utf-8'))
    except Exception as e:
        print(f"{lang}.json: ERROR loading file ({e})")
        continue
    def flatten(d, prefix='', out=None):
        out = {} if out is None else out
        if isinstance(d, dict):
            for k,v in d.items():
                new_prefix = f"{prefix}.{k}" if prefix else k
                flatten(v, new_prefix, out)
        else:
            out[prefix] = d
        return out
    flat = flatten(data)
    missing = [key for key in keys if key not in flat]
    status = "None" if not missing else ", ".join(missing)
    print(f"{lang}.json missing: {status}")
EOF

Length of output: 791


Add missing i18n keys for runstable placeholders and search criteria
Missing in all locale JSONs:

  • runstable.placeholder.title
  • runstable.placeholder.body
  • runstable.placeholder.search
  • runstable.search_criteria

(The runstable.sort_tooltip key is already present.)

🤖 Prompt for AI Agents
In src/components/run/RunsTable.tsx lines 381-403, the i18n keys used for the
empty-state and search copy are missing from the locale JSONs; add the following
keys to every locale file: "runstable.placeholder.title",
"runstable.placeholder.body", "runstable.placeholder.search", and
"runstable.search_criteria" (ensure the translations are supplied for each
locale). Also verify the component uses the intended key (it references
recordingtable.search_criteria in one place) and either add that key to locales
or change the component to use runstable.search_criteria so keys and code match.

Comment on lines +397 to +399
{searchTerm
? t('recordingtable.search_criteria')
: t('runstable.placeholder.body')
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix wrong i18n namespace for search criteria.

This references recordingtable.* inside RunsTable; should use runstable.*.

-              ? t('recordingtable.search_criteria')
+              ? t('runstable.search_criteria')
📝 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
{searchTerm
? t('recordingtable.search_criteria')
: t('runstable.placeholder.body')
{searchTerm
? t('runstable.search_criteria')
: t('runstable.placeholder.body')}
🤖 Prompt for AI Agents
In src/components/run/RunsTable.tsx around lines 397 to 399, the i18n key used
for the search criteria message is incorrectly namespaced as
recordingtable.search_criteria; change it to use the runstable namespace
(runstable.search_criteria) so it matches this component's translation keys and
returns the correct localized string.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bdafb18 and 561c3e8.

📒 Files selected for processing (6)
  • public/locales/de.json (2 hunks)
  • public/locales/en.json (2 hunks)
  • public/locales/es.json (2 hunks)
  • public/locales/ja.json (2 hunks)
  • public/locales/tr.json (2 hunks)
  • public/locales/zh.json (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • public/locales/zh.json
  • public/locales/en.json
🔇 Additional comments (7)
public/locales/de.json (1)

51-56: LGTM: Added empty-state strings for recordings

Wording and punctuation look natural. Keys align with likely usage in RecordingsTable.

public/locales/ja.json (2)

51-56: LGTM: 録画テーブルの空状態テキスト

自然な日本語で、コンポーネント用途にも合致しています。


99-103: LGTM: 実行テーブルの空状態テキスト

用語とトーンが既存文言と一貫しています。

public/locales/es.json (2)

51-56: LGTM: Mensajes de estado vacío para robots

Redacción clara y acorde al resto del archivo.


99-103: LGTM: Placeholders de Runs

Terminología “ejecuciones” consistente con el resto de la locale.

public/locales/tr.json (2)

51-56: LGTM: Kayıtlar için boş durum metinleri

Doğal ve arayüzle uyumlu.


99-103: LGTM: Çalıştırmalar için boş durum metinleri

Terim kullanımı tutarlı.

Comment on lines +99 to +103
"placeholder": {
"title": "Keine Durchläufe gefunden",
"body": "Hier werden alle Ihre Roboter-Durchläufe angezeigt. Sobald ein Roboter aktiv ist, werden seine Durchläufe hier protokolliert.",
"search": "Keine Durchläufe entsprechen Ihrer Suche"
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use “Ausführungen” consistently instead of “Durchläufe”

Elsewhere in this locale you use “Ausführungen”. Aligning avoids mixed terminology in the Runs UI.

Apply this diff:

-            "title": "Keine Durchläufe gefunden",
-            "body": "Hier werden alle Ihre Roboter-Durchläufe angezeigt. Sobald ein Roboter aktiv ist, werden seine Durchläufe hier protokolliert.",
-            "search": "Keine Durchläufe entsprechen Ihrer Suche"
+            "title": "Keine Ausführungen gefunden",
+            "body": "Hier werden alle Ihre Roboter-Ausführungen angezeigt. Sobald ein Roboter aktiv ist, werden seine Ausführungen hier protokolliert.",
+            "search": "Keine Ausführungen entsprechen Ihrer Suche"
📝 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
"placeholder": {
"title": "Keine Durchläufe gefunden",
"body": "Hier werden alle Ihre Roboter-Durchläufe angezeigt. Sobald ein Roboter aktiv ist, werden seine Durchläufe hier protokolliert.",
"search": "Keine Durchläufe entsprechen Ihrer Suche"
},
"placeholder": {
"title": "Keine Ausführungen gefunden",
"body": "Hier werden alle Ihre Roboter-Ausführungen angezeigt. Sobald ein Roboter aktiv ist, werden seine Ausführungen hier protokolliert.",
"search": "Keine Ausführungen entsprechen Ihrer Suche"
},
🤖 Prompt for AI Agents
In public/locales/de.json around lines 99 to 103, replace the word "Durchläufe"
with "Ausführungen" in the three placeholder strings so the localization uses
"Ausführungen" consistently; update "title" to "Keine Ausführungen gefunden",
"body" to "Hier werden alle Ihre Roboter-Ausführungen angezeigt. Sobald ein
Roboter aktiv ist, werden seine Ausführungen hier protokolliert.", and "search"
to "Keine Ausführungen entsprechen Ihrer Suche".

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

Caution

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

⚠️ Outside diff range comments (2)
src/components/robot/RecordingsTable.tsx (1)

252-271: Use persisted recording ID instead of array index (breaks delete/run/retrain + type mismatch)

Index-based IDs won’t match storage APIs (e.g., checkRunsForRecording/deleteRecordingFromStorage) and violate Data.id:string. Use the meta ID and drop rows without an ID.

-        const parsedRows = recordings
-        .map((recording: any, index: number) => {
-          if (recording?.recording_meta) {
-            const parsedDate = parseDateString(recording.recording_meta.updatedAt);
-            
-            return {
-              id: index,
-              ...recording.recording_meta,
-              content: recording.recording,
-              parsedDate
-            };
-          }
-          return null;
-        })
-        .filter(Boolean) 
-        .sort((a, b) => b.parsedDate.getTime() - a.parsedDate.getTime());
+        const parsedRows = recordings
+          .map((recording: any) => {
+            const meta = recording?.recording_meta;
+            if (!meta?.id) return null;
+            const parsedDate = parseDateString(meta.updatedAt);
+            return {
+              id: String(meta.id),
+              ...meta,
+              content: recording.recording,
+              parsedDate
+            };
+          })
+          .filter(Boolean)
+          .sort((a: any, b: any) => b.parsedDate.getTime() - a.parsedDate.getTime())
+          .map(({ parsedDate, ...row }: any) => row);
src/components/run/RunsTable.tsx (1)

191-198: Debounce is ineffective: a new debounced function is created per keystroke (multiple timeouts fire)
Make the debounced setter stable and fix the Timeout type for the browser.

-  const debouncedSearch = useCallback((fn: Function, delay: number) => {
-    let timeoutId: NodeJS.Timeout;
-    return (...args: any[]) => {
-      clearTimeout(timeoutId);
-      timeoutId = setTimeout(() => fn(...args), delay);
-    };
-  }, []);
+  const debouncedSearch = useCallback((fn: Function, delay: number) => {
+    let timeoutId: ReturnType<typeof setTimeout>;
+    return (...args: any[]) => {
+      clearTimeout(timeoutId);
+      timeoutId = setTimeout(() => fn(...args), delay);
+    };
+  }, []);
 
-  const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
-    const debouncedSetSearch = debouncedSearch((value: string) => {
-      setSearchTerm(value);
-      setAccordionPage(0);
-      setPaginationStates(prev => {
-        const reset = Object.keys(prev).reduce((acc, robotId) => ({
-          ...acc,
-          [robotId]: { ...prev[robotId], page: 0 }
-        }), {});
-        return reset;
-      });
-    }, 300);
-    debouncedSetSearch(event.target.value);
-  }, [debouncedSearch]);
+  const debouncedSetSearch = React.useMemo(
+    () =>
+      debouncedSearch((value: string) => {
+        setSearchTerm(value);
+        setAccordionPage(0);
+        setPaginationStates(prev =>
+          Object.fromEntries(
+            Object.entries(prev).map(([robotId, s]: any) => [robotId, { ...s, page: 0 }])
+          )
+        );
+      }, 300),
+    [debouncedSearch, setAccordionPage, setPaginationStates]
+  );
+
+  const handleSearchChange = useCallback(
+    (event: React.ChangeEvent<HTMLInputElement>) => {
+      debouncedSetSearch(event.target.value);
+    },
+    [debouncedSetSearch]
+  );

Also applies to: 199-213

♻️ Duplicate comments (3)
src/components/run/RunsTable.tsx (3)

432-474: Prefer MUI TableSortLabel for accessible sortable headers
Removes custom hover logic and adds keyboard/a11y.

-                            <TableCell
+                            <TableCell
                               key={column.id}
                               align={column.align}
-                              style={{ 
-                                minWidth: column.minWidth,
-                                cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default'
-                              }}
-                              onClick={() => {
-                                if (column.id === 'startedAt' || column.id === 'finishedAt') {
-                                  handleSort(column.id, robotMetaId);
-                                }
-                              }}
+                              style={{ minWidth: column.minWidth }}
                             >
-                              <Tooltip 
-                                title={
-                                  (column.id === 'startedAt' || column.id === 'finishedAt')
-                                    ? t('runstable.sort_tooltip')
-                                    : ''
-                                }
-                              >
-                                <Box sx={{ 
-                                  display: 'flex', 
-                                  alignItems: 'center', 
-                                  gap: 1,
-                                  '&:hover': {
-                                    '& .sort-icon': {
-                                      opacity: 1
-                                    }
-                                  }
-                                }}>
-                                  {column.label}
-                                  <Box className="sort-icon" sx={{ 
-                                    display: 'flex',
-                                    alignItems: 'center',
-                                    opacity: accordionSortConfigs[robotMetaId]?.field === column.id ? 1 : 0.3,
-                                    transition: 'opacity 0.2s'
-                                  }}>
-                                    {renderSortIcon(column, robotMetaId)}
-                                  </Box>
-                                </Box>
-                              </Tooltip>
+                              {(column.id === 'startedAt' || column.id === 'finishedAt') ? (
+                                <Tooltip title={t('runstable.sort_tooltip')}>
+                                  <Box>
+                                    <TableSortLabel
+                                      active={accordionSortConfigs[robotMetaId]?.field === column.id}
+                                      direction={accordionSortConfigs[robotMetaId]?.direction === 'desc' ? 'desc' : 'asc'}
+                                      onClick={() => handleSort(column.id as keyof Data, robotMetaId)}
+                                    >
+                                      {column.label}
+                                    </TableSortLabel>
+                                  </Box>
+                                </Tooltip>
+                              ) : (
+                                column.label
+                              )}
                             </TableCell>

Add near imports:

+import TableSortLabel from '@mui/material/TableSortLabel';

385-407: Verify i18n keys exist for the new empty-state strings

#!/bin/bash
# Verify required runstable keys exist in locale JSONs
rg -n --glob '!**/node_modules/**' \
  -e '"runstable\.placeholder\.title"' \
  -e '"runstable\.placeholder\.body"' \
  -e '"runstable\.placeholder\.search"' \
  -e '"runstable\.search_criteria"' \
  public/locales/*.json

403-405: Wrong i18n key namespace: use runstable.search_criteria
This will fall back or display the wrong string at runtime.

-              ? t('recordingtable.search_criteria')
+              ? t('runstable.search_criteria')
🧹 Nitpick comments (5)
src/components/robot/RecordingsTable.tsx (2)

519-521: Center the loading spinner for consistent UX with the empty-state block

-      {isLoading ? (
-        <CircularProgress size={32} />
-      ) : filteredRows.length === 0 ? (
+      {isLoading ? (
+        <Box display="flex" alignItems="center" justifyContent="center" sx={{ minHeight: 300 }}>
+          <CircularProgress size={32} />
+        </Box>
+      ) : filteredRows.length === 0 ? (

423-437: Don’t re-implement useDebounce here; import the shared hook to avoid duplication

-  function useDebounce<T>(value: T, delay: number): T {
-    const [debouncedValue, setDebouncedValue] = React.useState<T>(value);
-  
-    useEffect(() => {
-      const handler = setTimeout(() => {
-        setDebouncedValue(value);
-      }, delay);
-  
-      return () => {
-        clearTimeout(handler);
-      };
-    }, [value, delay]);
-  
-    return debouncedValue;
-  }
+  // Prefer: import useDebounce from '../../hooks/useDebounce';

If you want, I can submit a follow-up PR wiring the shared hook.

src/components/run/RunsTable.tsx (3)

385-387: Center the spinner to match the empty-state layout

-      {isLoading? (
-        <CircularProgress size={32} />
-      ) : Object.keys(groupedRows).length === 0 ? (
+      {isLoading? (
+        <Box display="flex" alignItems="center" justifyContent="center" sx={{ minHeight: 300 }}>
+          <CircularProgress size={32} />
+        </Box>
+      ) : Object.keys(groupedRows).length === 0 ? (

445-452: Avoid rendering Tooltip with an empty title on non-sortable columns
Small cleanup; conditionally render Tooltip only when needed.


224-225: “No runs” shouldn’t be an error-level notification
Use info/warning or rely on the empty-state UI only.

-        notify('error', t('runstable.notifications.no_runs'));
+        notify('info', t('runstable.notifications.no_runs'));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 561c3e8 and be0548f.

📒 Files selected for processing (2)
  • src/components/robot/RecordingsTable.tsx (5 hunks)
  • src/components/run/RunsTable.tsx (4 hunks)
🔇 Additional comments (1)
src/components/robot/RecordingsTable.tsx (1)

26-26: Loading-state wiring looks good

Also applies to: 158-159, 275-277, 411-413

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/robot/RecordingsTable.tsx (1)

248-271: Bug: row.id is a number but type and downstream APIs expect a string ID.

id is set from the array index; this breaks type consistency (Data.id is string) and may cause mismatches for delete/check APIs.

-            return {
-              id: index,
-              ...recording.recording_meta,
-              content: recording.recording,
-              parsedDate
-            };
+            return {
+              id: String(recording.recording_meta.id),
+              ...recording.recording_meta,
+              content: recording.recording,
+              parsedDate
+            };

Optional TS fix for the type after filter(Boolean):

-        .filter(Boolean) 
+        .filter((r): r is { id: string; name: string; parsedDate: Date; recording?: any; recording_meta?: any } => Boolean(r))
♻️ Duplicate comments (4)
src/components/robot/RecordingsTable.tsx (1)

531-552: Verify i18n keys exist for the new placeholders.

Run to confirm across locales:

#!/bin/bash
rg -n --glob '!**/node_modules/**' \
  -e '"recordingtable\.placeholder\.title"' \
  -e '"recordingtable\.placeholder\.body"' \
  -e '"recordingtable\.placeholder\.search"' \
  -e '"recordingtable\.search_criteria"' \
  public/locales/*.json
src/components/run/RunsTable.tsx (3)

441-483: Prefer MUI TableSortLabel for accessible sortable headers (keyboard + a11y).

-                            <TableCell
+                            <TableCell
                               key={column.id}
                               align={column.align}
-                              style={{ 
-                                minWidth: column.minWidth,
-                                cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default'
-                              }}
-                              onClick={() => {
-                                if (column.id === 'startedAt' || column.id === 'finishedAt') {
-                                  handleSort(column.id, robotMetaId);
-                                }
-                              }}
+                              style={{ minWidth: column.minWidth }}
                             >
-                              <Tooltip 
-                                title={
-                                  (column.id === 'startedAt' || column.id === 'finishedAt')
-                                    ? t('runstable.sort_tooltip')
-                                    : ''
-                                }
-                              >
-                                <Box sx={{ 
-                                  display: 'flex', 
-                                  alignItems: 'center', 
-                                  gap: 1,
-                                  '&:hover': {
-                                    '& .sort-icon': {
-                                      opacity: 1
-                                    }
-                                  }
-                                }}>
-                                  {column.label}
-                                  <Box className="sort-icon" sx={{ 
-                                    display: 'flex',
-                                    alignItems: 'center',
-                                    opacity: accordionSortConfigs[robotMetaId]?.field === column.id ? 1 : 0.3,
-                                    transition: 'opacity 0.2s'
-                                  }}>
-                                    {renderSortIcon(column, robotMetaId)}
-                                  </Box>
-                                </Box>
-                              </Tooltip>
+                              {column.id === 'startedAt' || column.id === 'finishedAt' ? (
+                                <TableSortLabel
+                                  active={accordionSortConfigs[robotMetaId]?.field === column.id}
+                                  direction={accordionSortConfigs[robotMetaId]?.direction === 'desc' ? 'desc' : 'asc'}
+                                  onClick={() => handleSort(column.id as keyof Data, robotMetaId)}
+                                  title={t('runstable.sort_tooltip')}
+                                >
+                                  {column.label}
+                                </TableSortLabel>
+                              ) : (
+                                column.label
+                              )}
                             </TableCell>

Add import:

+import TableSortLabel from '@mui/material/TableSortLabel';

398-419: Verify i18n keys exist for runstable placeholders and guidance.

#!/bin/bash
rg -n --glob '!**/node_modules/**' \
  -e '"runstable\.placeholder\.title"' \
  -e '"runstable\.placeholder\.body"' \
  -e '"runstable\.placeholder\.search"' \
  -e '"runstable\.search_criteria"' \
  public/locales/*.json

413-416: Wrong i18n namespace in empty-state body (uses recordingtable.*).

Switch to runstable.search_criteria so it resolves to the correct string.

-              ? t('recordingtable.search_criteria')
+              ? t('runstable.search_criteria')
🧹 Nitpick comments (5)
src/components/robot/RecordingsTable.tsx (3)

158-159: Ensure spinner shows during all fetches (set isLoading(true) before each fetch).

Currently isLoading starts true and is reset in finally, but rerender-triggered fetches won’t show the spinner. Set isLoading(true) at the start of fetchRecordings and before rerender-driven fetches.

 const fetchRecordings = useCallback(async () => {
-  try {
+  try {
+    setIsLoading(true);
     const recordings = await getStoredRecordings();
@@
   } catch (error) {
     console.error('Error fetching recordings:', error);
     notify('error', t('recordingtable.notifications.fetch_error'));
   } finally {
     setIsLoading(false);
   }
 }, [setRecordings, notify, t]);
@@
 useEffect(() => {
   if (rerenderRobots) {
-    fetchRecordings().then(() => {
+    setIsLoading(true);
+    fetchRecordings().then(() => {
       setRerenderRobots(false);
     });
   }
 }, [rerenderRobots, fetchRecordings, setRerenderRobots]);

Also applies to: 275-277, 411-413


519-531: Minor a11y: label the progress indicator.

-          <CircularProgress size={60} />
+          <CircularProgress size={60} aria-label={t('common.loading', 'Loading')} />

495-516: Use Button instead of IconButton for a text button.

Improves semantics/a11y; keep styles via sx and use startIcon.

-          <IconButton
-            aria-label="new"
-            size={"small"}
+          <Button
             onClick={handleNewRecording}
             sx={{
               width: '140px',
               borderRadius: '5px',
               padding: '8px',
               background: '#ff00c3',
               color: 'white',
               marginRight: '10px',
               fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
               fontWeight: '500',
               fontSize: '0.875rem',
               lineHeight: '1.75',
               letterSpacing: '0.02857em',
               '&:hover': { color: 'white', backgroundColor: '#ff00c3' }
             }}
           >
-            <Add sx={{ marginRight: '5px' }} /> {t('recordingtable.new')}
-          </IconButton>
+            <Add sx={{ mr: 1 }} /> {t('recordingtable.new')}
+          </Button>
src/components/run/RunsTable.tsx (2)

385-397: Minor a11y: label the progress indicator.

-          <CircularProgress size={60} />
+          <CircularProgress size={60} aria-label={t('common.loading', 'Loading')} />

191-213: Debounce is ineffective; new debounced function is created on every keystroke.

Timers aren’t cleared across events. Keep a stable debounced function or use a debounce hook.

-  const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
-    const debouncedSetSearch = debouncedSearch((value: string) => {
-      setSearchTerm(value);
-      setAccordionPage(0);
-      setPaginationStates(prev => {
-        const reset = Object.keys(prev).reduce((acc, robotId) => ({
-          ...acc,
-          [robotId]: { ...prev[robotId], page: 0 }
-        }), {});
-        return reset;
-      });
-    }, 300);
-    debouncedSetSearch(event.target.value);
-  }, [debouncedSearch]);
+  const debouncedSetSearch = React.useMemo(
+    () =>
+      debouncedSearch((value: string) => {
+        setSearchTerm(value);
+        setAccordionPage(0);
+        setPaginationStates(prev =>
+          Object.fromEntries(Object.entries(prev).map(([id, s]) => [id, { ...s, page: 0 }]))
+        );
+      }, 300),
+    [debouncedSearch]
+  );
+
+  const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
+    debouncedSetSearch(event.target.value);
+  }, [debouncedSetSearch]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between be0548f and 83f0497.

📒 Files selected for processing (2)
  • src/components/robot/RecordingsTable.tsx (5 hunks)
  • src/components/run/RunsTable.tsx (4 hunks)
🔇 Additional comments (3)
src/components/robot/RecordingsTable.tsx (1)

26-27: LGTM: import for spinner is correct and used.

src/components/run/RunsTable.tsx (2)

12-12: LGTM: spinner import is correct.


137-138: Tri-state loading/empty/data flow looks good.

isLoading gating with finally reset is correctly applied and shows a centered spinner on initial and rerender fetches.

Also applies to: 228-230, 236-243, 385-397

@amhsirak amhsirak merged commit a53e4f0 into getmaxun:develop Sep 10, 2025
1 check 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.

Feat: add placeholder for empty runs & robots UI

2 participants