Skip to content

First playwright smokescreen test#1410

Merged
aliasaria merged 4 commits intomainfrom
add/smokescreenplaywrite
Mar 3, 2026
Merged

First playwright smokescreen test#1410
aliasaria merged 4 commits intomainfrom
add/smokescreenplaywrite

Conversation

@aliasaria
Copy link
Copy Markdown
Member

@aliasaria aliasaria commented Feb 26, 2026

To tests, run the docker container using:

npm run docker-test:up

Then run the tests using:

npx playwright test smoke-screens

or

npx playwright test smoke-screens --headed to see it

Summary by CodeRabbit

  • New Features
    • Added an in-app announcement banner that shows server-sent announcements, supports markdown, and can be dismissed (respects viewed state).
  • Removed
    • Removed the previous announcements modal from the app.
  • Tests
    • Added Playwright smoke tests covering main screen load, login, and primary navigation flows.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a946245 and f699a42.

📒 Files selected for processing (1)
  • src/renderer/App.tsx

📝 Walkthrough

Walkthrough

Replaces the modal-based announcements flow with a new dismissible AnnouncementBanner rendered in AppContent, removes the AnnouncementsModal, and adds a Playwright smoke-test suite that logs in and exercises main-screen navigation across multiple app areas.

Changes

Cohort / File(s) Summary
App integration
src/renderer/App.tsx
Replaced rendering of AnnouncementsModal with AnnouncementBanner inside the main AppContent layout.
New UI component
src/renderer/components/Shared/AnnouncementBanner.tsx
Adds AnnouncementBanner component: authenticated fetch of /server/announcements, normalize/sort/filter by expiry, track last-viewed in persistent storage, render dismissible markdown banner, include FORCE_SHOW dev flag and error handling.
Removed UI component
src/renderer/components/Shared/AnnouncementsModal.tsx
Removes previous modal-based announcements component and its fetching, parsing, storage, and modal UI logic.
Playwright tests
test/playwright/smoke-screens.spec.ts
New smoke-test suite with login and selectFirstExperiment helpers; verifies login, welcome screen, and navigation across Model Registry, Datasets, Tasks Gallery (conditional), Compute (conditional), Interact, Tasks, Documents, and Notes screens.

Sequence Diagram(s)

sequenceDiagram
  participant App as App (renders)
  participant Banner as AnnouncementBanner
  participant API as Server API
  participant Store as LocalStorage
  rect rgba(200,200,255,0.5)
    App->>Banner: mount
  end
  rect rgba(200,255,200,0.5)
    Banner->>API: GET /server/announcements (authenticated)
    API-->>Banner: announcement list
    Banner->>Banner: normalize, filter by expiry, sort by date
    Banner->>Store: read lastViewedDate
    alt has unseen announcement
      Banner-->>App: render banner with content
    else no valid announcement
      Banner-->>App: render nothing
    end
  end
  rect rgba(255,200,200,0.5)
    App->>Banner: user clicks dismiss
    Banner->>Store: write lastViewedDate (announcement.date)
    Banner-->>App: hide banner
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions only the Playwright smoke test addition but does not reflect the primary changes: refactoring announcements from modal to banner and removing the legacy modal component. Update the title to better reflect all major changes, such as: 'Refactor announcements UI to banner and add smoke screen tests' or focus on the primary change if the smoke test is secondary.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add/smokescreenplaywrite

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

@paragon-review
Copy link
Copy Markdown

Paragon Summary

This pull request review identified 2 issues across 2 categories in 2 files. The review analyzed code changes, potential bugs, security vulnerabilities, performance issues, and code quality concerns using automated analysis tools.

This PR introduces the first Playwright smoke screen test suite for the application, establishing basic end-to-end testing capability that can be run via Docker. It adds a new smoke-screens test specification and makes corresponding updates to the App component to support testability.

Key changes:

  • Added Playwright smoke screen test suite (smoke-screens.spec.ts)
  • Modified App.tsx (likely for test compatibility)
  • Tests run via Docker container (npm run docker-test:up)
  • Supports headed mode for visual debugging (--headed flag)

Confidence score: 3/5

  • This PR has moderate risk due to 1 high-priority issue that should be addressed
  • Score reflects significant bugs, performance issues, or architectural concerns
  • Review high-priority findings carefully before merging

2 files reviewed, 2 comments

Severity breakdown: High: 1, Medium: 1


Tip: @paragon-run <instructions> to chat with our agent or push fixes!

Dashboard

Comment thread src/renderer/App.tsx Outdated
</Box>
)}
<AnnouncementsModal />
{/* <AnnouncementsModal /> */}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Production feature disabled for test convenience

Production feature disabled for test convenience. Users won't see announcements. Handle modal in test code instead.

View Details

Location: src/renderer/App.tsx (lines 203)

Analysis

Production feature disabled for test convenience. Users won't see announcements

What fails AnnouncementsModal component is commented out in production code to make tests pass
Result Users do not see any announcement notifications that should be displayed
Expected AnnouncementsModal should render normally; tests should dismiss or handle the modal in test code
Impact Production functionality is broken for users; anti-pattern of modifying production code to suit tests
How to reproduce
Start the app and navigate to any screen - announcements modal will never appear
Patch Details
-      {/* <AnnouncementsModal /> */}
+      <AnnouncementsModal />
AI Fix Prompt
Fix this issue: Production feature disabled for test convenience. Users won't see announcements. Handle modal in test code instead.

Location: src/renderer/App.tsx (lines 203)
Problem: AnnouncementsModal component is commented out in production code to make tests pass
Current behavior: Users do not see any announcement notifications that should be displayed
Expected: AnnouncementsModal should render normally; tests should dismiss or handle the modal in test code
Steps to reproduce: Start the app and navigate to any screen - announcements modal will never appear

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

});
}

async function selectFirstExperiment(page: Page) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Test assumes existing experiment data

Test assumes existing experiment data. Fresh environments cause timeout errors. Add fixture to create experiment.

View Details

Location: test/playwright/smoke-screens.spec.ts (lines 24)

Analysis

Test assumes existing experiment data. Fresh environments cause timeout errors

What fails selectFirstExperiment function times out when no experiments exist in the test environment
Result Tests for Interact, Tasks, Documents, Notes screens fail with timeout waiting for menuitem
Expected Tests should either create an experiment fixture or skip gracefully when none exist
Impact Flaky tests that fail in CI/fresh environments; unreliable test suite
How to reproduce
Run tests in fresh environment with no experiments: npx playwright test smoke-screens
AI Fix Prompt
Fix this issue: Test assumes existing experiment data. Fresh environments cause timeout errors. Add fixture to create experiment.

Location: test/playwright/smoke-screens.spec.ts (lines 24)
Problem: selectFirstExperiment function times out when no experiments exist in the test environment
Current behavior: Tests for Interact, Tasks, Documents, Notes screens fail with timeout waiting for menuitem
Expected: Tests should either create an experiment fixture or skip gracefully when none exist
Steps to reproduce: Run tests in fresh environment with no experiments: npx playwright test smoke-screens

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

Copy link
Copy Markdown
Contributor

@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: 2

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

Inline comments:
In `@src/renderer/App.tsx`:
- Line 203: The AnnouncementsModal component was commented out in App.tsx (the
line with {/* <AnnouncementsModal /> */}) which disables announcements for all
users; restore the normal product behavior by re-enabling the
<AnnouncementsModal /> render inside the App component, and if you must avoid it
during automated tests, add a clear, explicit runtime guard (e.g., an isE2ETest
/ process.env.E2E flag or a window.__PLAYWRIGHT__ check) around the
<AnnouncementsModal /> render so it only skips in test runs—do not leave it
commented out globally and prefer handling test-specific suppression from
Playwright or an e2e-only flag.

In `@test/playwright/smoke-screens.spec.ts`:
- Around line 10-16: The login helper currently hard-codes credentials in the
login(page: Page) function; change it to read email and password from
environment variables (e.g., process.env.E2E_ADMIN_EMAIL and
process.env.E2E_ADMIN_PASSWORD), validate they are defined and throw a clear
error (fail fast) if missing, then use those variables in the calls to
page.getByPlaceholder('Email Address').fill(...) and
page.getByPlaceholder('Password').fill(...) before clicking the Sign In button;
this removes secrets from source and makes CI configurable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4f3bae and cde3ba5.

📒 Files selected for processing (2)
  • src/renderer/App.tsx
  • test/playwright/smoke-screens.spec.ts

Comment thread src/renderer/App.tsx Outdated
</Box>
)}
<AnnouncementsModal />
{/* <AnnouncementsModal /> */}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore AnnouncementsModal for non-test runtime.

Line 203 disables announcements for all users, which is a product behavior change unrelated to smoke-test scope. Please render it again and handle test interference inside Playwright (or via an explicit e2e-only flag).

Suggested minimal fix
-      {/* <AnnouncementsModal /> */}
+      <AnnouncementsModal />
📝 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
{/* <AnnouncementsModal /> */}
<AnnouncementsModal />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/App.tsx` at line 203, The AnnouncementsModal component was
commented out in App.tsx (the line with {/* <AnnouncementsModal /> */}) which
disables announcements for all users; restore the normal product behavior by
re-enabling the <AnnouncementsModal /> render inside the App component, and if
you must avoid it during automated tests, add a clear, explicit runtime guard
(e.g., an isE2ETest / process.env.E2E flag or a window.__PLAYWRIGHT__ check)
around the <AnnouncementsModal /> render so it only skips in test runs—do not
leave it commented out globally and prefer handling test-specific suppression
from Playwright or an e2e-only flag.

Comment on lines +10 to +16
async function login(page: Page) {
await page.goto('/');

// Fill in credentials and submit
await page.getByPlaceholder('Email Address').fill('[email protected]');
await page.getByPlaceholder('Password').fill('admin123');
await page.getByRole('button', { name: 'Sign In' }).click();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hard-coded login credentials in e2e tests.

Lines 14-16 hard-code credentials, which is brittle in CI and a security hygiene risk. Read credentials from environment variables and fail fast when missing.

Suggested fix
 async function login(page: Page) {
   await page.goto('/');
+  const email = process.env.E2E_EMAIL;
+  const password = process.env.E2E_PASSWORD;
+  if (!email || !password) {
+    throw new Error('Missing E2E_EMAIL/E2E_PASSWORD for smoke tests');
+  }
 
   // Fill in credentials and submit
-  await page.getByPlaceholder('Email Address').fill('[email protected]');
-  await page.getByPlaceholder('Password').fill('admin123');
+  await page.getByPlaceholder('Email Address').fill(email);
+  await page.getByPlaceholder('Password').fill(password);
   await page.getByRole('button', { name: 'Sign In' }).click();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/playwright/smoke-screens.spec.ts` around lines 10 - 16, The login helper
currently hard-codes credentials in the login(page: Page) function; change it to
read email and password from environment variables (e.g.,
process.env.E2E_ADMIN_EMAIL and process.env.E2E_ADMIN_PASSWORD), validate they
are defined and throw a clear error (fail fast) if missing, then use those
variables in the calls to page.getByPlaceholder('Email Address').fill(...) and
page.getByPlaceholder('Password').fill(...) before clicking the Sign In button;
this removes secrets from source and makes CI configurable.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
src/renderer/components/Shared/AnnouncementBanner.tsx (1)

16-19: Add unit tests for the utility and hook decision logic.

This change adds utility/date logic and a complex selection flow (expired vs unseen vs latest) but no targeted unit coverage.

As per coding guidelines, "Write unit tests for all utility functions and complex hooks in frontend".

Also applies to: 54-145

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

In `@src/renderer/components/Shared/AnnouncementBanner.tsx` around lines 16 - 19,
Add unit tests covering the new date utility and the announcement selection
logic: write tests for parseLocalDate (verify correct Date for various
YYYY-MM-DD inputs, invalid strings, and timezone neutrality) and for the
announcement selection flow in the hook that decides expired vs unseen vs latest
(exercise cases where announcements are expired, already seen, unseen, and where
multiple candidates exist to ensure the hook returns the correct announcement).
Target the exported utility parseLocalDate and the hook/function responsible for
selection (the selection logic within lines ~54-145) and include edge cases
(empty lists, all expired, duplicate timestamps) to satisfy the "utility
functions and complex hooks" guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/components/Shared/AnnouncementBanner.tsx`:
- Around line 180-188: The IconButton in AnnouncementBanner.tsx that calls
handleDismiss is icon-only and lacks an accessible name; update the IconButton
(the one rendering XIcon and using onClick={handleDismiss}) to provide an
accessible label by adding an aria-label (e.g., aria-label="Dismiss
announcement") or aria-labelledby pointing to visible/visually-hidden text, or a
title prop as fallback, so screen readers can announce the button's purpose
while preserving current styling and behavior.
- Around line 16-19: The issue is that parseLocalDate returns a Date at midnight
so comparisons against now make same-day expiries effective at 00:00; change
parseLocalDate(dateString: string) to produce the end of that local day (e.g.,
new Date(year, month - 1, day, 23, 59, 59, 999)) so that checks in the
AnnouncementBanner logic (places where parseLocalDate() is compared to now in
the component) treat the entire expiry day as valid; update the single
parseLocalDate function (used by the expiry checks) accordingly so all
comparisons (the existing comparisons that currently use parseLocalDate) behave
correctly.
- Around line 54-57: The effect calls API_URL() but doesn't list it as a
dependency, so extract the result of API_URL() to a component-level constant
(e.g., const apiUrl = API_URL()) and update the useEffect to depend on apiUrl
and hasChecked; change the early-return to use apiUrl (if (!apiUrl ||
hasChecked) return) and ensure any logic that previously called API_URL() inside
the effect now uses apiUrl so the effect re-runs when API_URL transitions from
null to a value.
- Around line 23-25: Remove the unnecessary any casts around window.storage in
getStoredDate (and any other uses of (window as any).storage) and use the
globally declared StorageAPI type directly (e.g., reference
window.storage.get/localStorageKey) to restore type-safety; also define a typed
interface for the announcement API response (e.g., IAnnouncementResponse) and
replace untyped accesses to result.data with the typed shape when parsing the
fetch result in the component (the code path that reads result.data), ensuring
you validate/guard optional fields before use.

---

Nitpick comments:
In `@src/renderer/components/Shared/AnnouncementBanner.tsx`:
- Around line 16-19: Add unit tests covering the new date utility and the
announcement selection logic: write tests for parseLocalDate (verify correct
Date for various YYYY-MM-DD inputs, invalid strings, and timezone neutrality)
and for the announcement selection flow in the hook that decides expired vs
unseen vs latest (exercise cases where announcements are expired, already seen,
unseen, and where multiple candidates exist to ensure the hook returns the
correct announcement). Target the exported utility parseLocalDate and the
hook/function responsible for selection (the selection logic within lines
~54-145) and include edge cases (empty lists, all expired, duplicate timestamps)
to satisfy the "utility functions and complex hooks" guideline.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cde3ba5 and a946245.

📒 Files selected for processing (3)
  • src/renderer/App.tsx
  • src/renderer/components/Shared/AnnouncementBanner.tsx
  • src/renderer/components/Shared/AnnouncementsModal.tsx
💤 Files with no reviewable changes (1)
  • src/renderer/components/Shared/AnnouncementsModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/App.tsx

Comment on lines +16 to +19
function parseLocalDate(dateString: string): Date {
const [year, month, day] = dateString.split('-').map(Number);
return new Date(year, month - 1, day);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "AnnouncementBanner.tsx"

Repository: transformerlab/transformerlab-app

Length of output: 130


🏁 Script executed:

cat -n src/renderer/components/Shared/AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 6891


Fix same-day expiry handling (currently expires at start of day).

Lines 87, 100, and 117 compare parseLocalDate() results (which return dates at midnight) against now (current time including hours/minutes). This causes announcements with expires: "YYYY-MM-DD" to expire immediately after 00:00 on that day instead of remaining valid throughout the day.

Proposed fix
 function parseLocalDate(dateString: string): Date {
   const [year, month, day] = dateString.split('-').map(Number);
   return new Date(year, month - 1, day);
 }
+
+function startOfDay(date: Date): Date {
+  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
+}
@@
-        const now = new Date();
+        const now = new Date();
+        const today = startOfDay(now);
@@
-          if (parseLocalDate(latestOverall.expires) < now) {
+          if (parseLocalDate(latestOverall.expires) < today) {
@@
-          if (isNewer && a.expires && parseLocalDate(a.expires) < now) {
+          if (isNewer && a.expires && parseLocalDate(a.expires) < today) {
@@
-          (a) => !a.expires || parseLocalDate(a.expires) >= now,
+          (a) => !a.expires || parseLocalDate(a.expires) >= today,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Shared/AnnouncementBanner.tsx` around lines 16 - 19,
The issue is that parseLocalDate returns a Date at midnight so comparisons
against now make same-day expiries effective at 00:00; change
parseLocalDate(dateString: string) to produce the end of that local day (e.g.,
new Date(year, month - 1, day, 23, 59, 59, 999)) so that checks in the
AnnouncementBanner logic (places where parseLocalDate() is compared to now in
the component) treat the entire expiry day as valid; update the single
parseLocalDate function (used by the expiry checks) accordingly so all
comparisons (the existing comparisons that currently use parseLocalDate) behave
correctly.

Comment on lines +23 to +25
async function getStoredDate(): Promise<string | null> {
if ((window as any).storage) {
return (window as any).storage.get(localStorageKey);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd src/renderer/components/Shared && wc -l AnnouncementBanner.tsx && head -80 AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 2453


🏁 Script executed:

cd src/renderer/components/Shared && tail -120 AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 3422


🏁 Script executed:

rg "window\.storage|\.storage\." --type ts --type tsx -B 2 -A 2

Repository: transformerlab/transformerlab-app

Length of output: 104


🏁 Script executed:

rg "window\.storage|\.storage\." -B 2 -A 2

Repository: transformerlab/transformerlab-app

Length of output: 7662


🏁 Script executed:

rg "interface.*Window|Window.*interface|declare.*global" -B 3 -A 5 | head -50

Repository: transformerlab/transformerlab-app

Length of output: 621


🏁 Script executed:

cat -n src/renderer/preload.d.ts

Repository: transformerlab/transformerlab-app

Length of output: 811


🏁 Script executed:

cat -n src/renderer/preload.d.ts | grep -A 5 "StorageAPI"

Repository: transformerlab/transformerlab-app

Length of output: 433


Remove any casts—window.storage is already properly typed.

Lines 24 and 31 use (window as any).storage, but window.storage is already declared as StorageAPI in the global scope (src/renderer/preload.d.ts). The casts are unnecessary and violate the type-safety guideline.

Additionally, line 68 should type the API response structure to avoid relying on unvalidated result.data.

Proposed fix
+interface AnnouncementsResponse {
+  data?: Announcement[];
+}
+
 async function getStoredDate(): Promise<string | null> {
-  if ((window as any).storage) {
-    return (window as any).storage.get(localStorageKey);
+  if (window.storage) {
+    return window.storage.get(localStorageKey);
   }
   return localStorage.getItem(localStorageKey);
 }
 
 async function setStoredDate(date: string): Promise<void> {
-  if ((window as any).storage) {
-    await (window as any).storage.set(localStorageKey, date);
+  if (window.storage) {
+    await window.storage.set(localStorageKey, date);
   } else {
     localStorage.setItem(localStorageKey, date);
   }
@@
-        const result = await response.json();
-        const announcements: Announcement[] = result.data || [];
+        const result = (await response.json()) as AnnouncementsResponse;
+        const announcements: Announcement[] = Array.isArray(result.data)
+          ? result.data
+          : [];

Per coding guidelines: "Avoid any in TypeScript; define interfaces for all props and API responses to ensure type safety."

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

In `@src/renderer/components/Shared/AnnouncementBanner.tsx` around lines 23 - 25,
Remove the unnecessary any casts around window.storage in getStoredDate (and any
other uses of (window as any).storage) and use the globally declared StorageAPI
type directly (e.g., reference window.storage.get/localStorageKey) to restore
type-safety; also define a typed interface for the announcement API response
(e.g., IAnnouncementResponse) and replace untyped accesses to result.data with
the typed shape when parsing the fetch result in the component (the code path
that reads result.data), ensuring you validate/guard optional fields before use.

Comment on lines +54 to +57
useEffect(() => {
const isConnected = API_URL() !== null;
if (!isConnected || hasChecked) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "AnnouncementBanner.tsx" | head -5

Repository: transformerlab/transformerlab-app

Length of output: 132


🏁 Script executed:

cat -n ./src/renderer/components/Shared/AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 6891


🏁 Script executed:

find . -type f -name "urls.ts" -o -name "urls.js" | grep -E "api-client|lib" | head -10

Repository: transformerlab/transformerlab-app

Length of output: 114


🏁 Script executed:

cat -n ./src/renderer/lib/api-client/urls.ts

Repository: transformerlab/transformerlab-app

Length of output: 3140


Add API_URL to the effect dependency array.

The useEffect calls API_URL() on lines 55 and 61, but the dependency array at line 145 only includes hasChecked. If API_URL() becomes available after the initial render (i.e., transitions from null to a value), the effect will never re-run to fetch announcements.

Extract API_URL() to a component-level variable and add it to the dependency array:

Proposed fix
 export default function AnnouncementBanner() {
+  const apiUrl = API_URL();
   const [currentAnnouncement, setCurrentAnnouncement] =
@@
   useEffect(() => {
-    const isConnected = API_URL() !== null;
-    if (!isConnected || hasChecked) return;
+    if (!apiUrl || hasChecked) return;
@@
         const response = await chatAPI.authenticatedFetch(
-          `${API_URL()}server/announcements`,
+          `${apiUrl}server/announcements`,
         );
@@
-  }, [hasChecked]);
+  }, [apiUrl, hasChecked]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Shared/AnnouncementBanner.tsx` around lines 54 - 57,
The effect calls API_URL() but doesn't list it as a dependency, so extract the
result of API_URL() to a component-level constant (e.g., const apiUrl =
API_URL()) and update the useEffect to depend on apiUrl and hasChecked; change
the early-return to use apiUrl (if (!apiUrl || hasChecked) return) and ensure
any logic that previously called API_URL() inside the effect now uses apiUrl so
the effect re-runs when API_URL transitions from null to a value.

Comment on lines +180 to +188
<IconButton
size="sm"
variant="plain"
color="neutral"
onClick={handleDismiss}
sx={{ mt: 0.25 }}
>
<XIcon size={16} />
</IconButton>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd src/renderer/components/Shared && wc -l AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 103


🏁 Script executed:

cd src/renderer/components/Shared && sed -n '175,195p' AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 440


🏁 Script executed:

# Also check what IconButton is being imported from
cd src/renderer/components/Shared && head -30 AnnouncementBanner.tsx | grep -E "import|from"

Repository: transformerlab/transformerlab-app

Length of output: 410


🏁 Script executed:

# Check if aria-label is used on other IconButton instances in the codebase
rg "IconButton" src/renderer --type tsx -A 3 | grep -i "aria-label" | head -20

Repository: transformerlab/transformerlab-app

Length of output: 104


🏁 Script executed:

# Get broader context - check the entire AnnouncementBanner component
cat -n src/renderer/components/Shared/AnnouncementBanner.tsx

Repository: transformerlab/transformerlab-app

Length of output: 6891


🏁 Script executed:

# Search for aria-label usage patterns in similar button/icon components
rg "aria-label" src/renderer --type tsx -B 2 -A 1 | head -40

Repository: transformerlab/transformerlab-app

Length of output: 104


🏁 Script executed:

# Search for aria-label usage in the codebase (without file type)
rg "aria-label" src/renderer -B 2 -A 1 | head -50

Repository: transformerlab/transformerlab-app

Length of output: 3153


🏁 Script executed:

# Also check MUI Joy documentation knowledge - search for IconButton with aria in codebase
rg "IconButton" src/renderer -B 1 -A 5 | head -60

Repository: transformerlab/transformerlab-app

Length of output: 4254


🏁 Script executed:

# Search more specifically for IconButton usage with children but looking for aria patterns
rg "IconButton" src/renderer -B 2 -A 10 | grep -E "(IconButton|aria-label|title)" | head -40

Repository: transformerlab/transformerlab-app

Length of output: 3800


🏁 Script executed:

# Check if there are any icon-only buttons with aria-label in the codebase
rg -B 3 "IconButton" src/renderer | rg -B 3 "aria-label"

Repository: transformerlab/transformerlab-app

Length of output: 1005


🏁 Script executed:

# Let's look at one of those examples more closely to see the full IconButton usage with aria-label
rg -B 5 -A 5 "aria-label=\"Stop generation\"" src/renderer

Repository: transformerlab/transformerlab-app

Length of output: 1161


🏁 Script executed:

# Check MUI Joy IconButton component - search for its usage with aria attributes in other files
rg -B 3 -A 8 "IconButton" src/renderer/components/Experiment/Interact/VisualizeGeneration.tsx | grep -A 8 "aria-label"

Repository: transformerlab/transformerlab-app

Length of output: 334


Add an accessible name to the dismiss button.

The IconButton at lines 180-188 is icon-only without an accessible label, so screen readers won't announce its purpose. This violates WCAG 2.1 Level A requirements for button names.

Proposed fix
       <IconButton
+        aria-label="Dismiss announcement"
         size="sm"
         variant="plain"
         color="neutral"
         onClick={handleDismiss}
         sx={{ mt: 0.25 }}
       >
📝 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
<IconButton
size="sm"
variant="plain"
color="neutral"
onClick={handleDismiss}
sx={{ mt: 0.25 }}
>
<XIcon size={16} />
</IconButton>
<IconButton
aria-label="Dismiss announcement"
size="sm"
variant="plain"
color="neutral"
onClick={handleDismiss}
sx={{ mt: 0.25 }}
>
<XIcon size={16} />
</IconButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Shared/AnnouncementBanner.tsx` around lines 180 -
188, The IconButton in AnnouncementBanner.tsx that calls handleDismiss is
icon-only and lacks an accessible name; update the IconButton (the one rendering
XIcon and using onClick={handleDismiss}) to provide an accessible label by
adding an aria-label (e.g., aria-label="Dismiss announcement") or
aria-labelledby pointing to visible/visually-hidden text, or a title prop as
fallback, so screen readers can announce the button's purpose while preserving
current styling and behavior.

@aliasaria aliasaria merged commit cf632db into main Mar 3, 2026
2 of 3 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.

1 participant