Skip to content

Conversation

@yumesha
Copy link
Contributor

@yumesha yumesha commented Jan 6, 2026

Summary

Fixes background image not loading when switching between projects by ensuring project settings are properly loaded on mount.

Problem

Background images were not displaying correctly due to missing project settings initialization in the root layout. The E2E test board-background-persistence.spec.ts was failing because the useProjectSettingsLoader hook wasn't being invoked.

Solution

  • Added useProjectSettingsLoader() hook to root layout component (apps/ui/src/routes/__root.tsx)
  • Ensures project settings (including background image preferences) load when component mounts
  • Hook triggers on project switches to refresh settings

Changes

  • apps/ui/src/routes/__root.tsx: Import and invoke useProjectSettingsLoader() hook

Testing

  • E2E test: board-background-persistence.spec.ts verifies background loads on project switch
  • E2E test: Verifies background loads on app restart
  • Manual testing: Background image displays correctly when switching between projects

Summary by CodeRabbit

  • New Features

    • Project-specific UI settings now automatically load when switching projects and persist across app restarts (board background, opacity levels, borders, glassmorphism, scrollbar hiding).
  • Tests

    • End-to-end tests added to validate loading, persisting, and re-applying UI settings across project switches and after app restarts.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Loads project-specific UI settings from the server when the current project changes and applies them to the app store via a new React hook; the hook is invoked in the root layout and covered by an end-to-end Playwright test that verifies persistence across project switches and restarts. (50 words)

Changes

Cohort / File(s) Summary
Project Settings Loader Hook
apps/ui/src/hooks/use-project-settings-loader.ts
New React hook that fetches project settings from the server when currentProject.path changes, prevents duplicate loads via an internal ref, and applies settings (boardBackground, cardOpacity, columnOpacity, columnBorderEnabled, cardGlassmorphism, cardBorderEnabled, cardBorderOpacity, hideScrollbar) to the app store; errors are logged.
Root Layout Integration
apps/ui/src/routes/__root.tsx
Imports and invokes useProjectSettingsLoader inside the root layout so project settings load when the app mounts and when currentProject.path changes.
E2E Tests: Board Background Persistence
apps/ui/tests/projects/board-background-persistence.spec.ts
New Playwright tests that create temp projects/files, seed localStorage, intercept /api/settings/project calls, and validate loading/persisting of board background settings across project switches and simulated app restarts.

Sequence Diagram

sequenceDiagram
    participant App as Root Layout
    participant Hook as useProjectSettingsLoader
    participant Store as App Store
    participant API as HTTP API Server

    App->>Hook: invoke on mount
    Store->>Hook: currentProject.path changes
    Hook->>Hook: skip if lastLoadedRef == path

    rect rgb(220,235,255)
        Hook->>API: GET /api/settings/project?path=currentProject.path
        API-->>Hook: 200 { project settings }
    end

    rect rgb(200,235,200)
        Hook->>Store: setBoardBackground(...)
        Hook->>Store: setCardOpacity(...)
        Hook->>Store: setColumnOpacity(...)
        Hook->>Store: setColumnBorderEnabled(...)
        Hook->>Store: setCardGlassmorphism(...)
        Hook->>Store: setCardBorderEnabled(...)
        Hook->>Store: setCardBorderOpacity(...)
        Hook->>Store: setHideScrollbar(...)
    end

    alt API error
        API--xHook: error
        Hook->>Hook: console.error(...)
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

🐰 I hop between projects, soft and spry,
Fetching colors from the server sky.
Backgrounds return with opacities bright,
Boards feel cozy, everything's right. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title partially describes the changeset by mentioning 'fix background image' and 'create test', which aligns with the PR's main objectives of fixing background image loading and adding E2E tests. However, it uses informal phrasing ('incase' instead of 'in case') and is somewhat generic about the specific problem being solved.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d608d8c and 9b30258.

📒 Files selected for processing (1)
  • apps/ui/src/hooks/use-project-settings-loader.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/hooks/use-project-settings-loader.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/hooks/use-project-settings-loader.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (4)
apps/ui/src/hooks/use-project-settings-loader.ts (4)

5-22: LGTM! Well-structured hook with proper race condition protection.

The dual-ref approach (loadingRef to prevent duplicate loads, currentProjectRef to ignore stale async results) correctly addresses the rapid project-switching scenario from previous reviews.


24-47: Race condition protection is correctly implemented.

The pattern of capturing requestedProjectPath before the async call and comparing it against currentProjectRef.current after the await properly handles the A → B → A rapid switching scenario. Stale results will be ignored if the user has switched to a different project.


76-83: LGTM! Dependency array correctly simplified.

The setter functions from Zustand are stable references, so excluding them from the dependency array is correct. The effect will only re-run when currentProject?.path changes, which is the intended behavior.


68-74: Settings structure is correct.

All properties in the settingsMap (cardOpacity, columnOpacity, columnBorderEnabled, cardGlassmorphism, cardBorderEnabled, cardBorderOpacity, hideScrollbar) are properly defined in the BoardBackgroundSettings type and exist only nested under boardBackground, not at the ProjectSettings level. The code correctly reads from bg?.key as intended.


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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @yumesha, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a crucial fix for the inconsistent loading of board background images and associated project settings. By implementing a dedicated React hook that proactively retrieves and applies these settings within the root layout, the application now ensures a seamless and persistent display of user-configured board aesthetics. The changes are thoroughly validated with new end-to-end tests, guaranteeing the reliability of this functionality.

Highlights

  • Bug Fix: Resolved an issue where board background images and other project settings failed to load correctly when switching between projects or restarting the application.
  • New Hook: Introduced a new React hook, useProjectSettingsLoader, responsible for fetching and applying project-specific settings from the server.
  • Root Layout Integration: The useProjectSettingsLoader hook is now invoked in the root layout component (apps/ui/src/routes/__root.tsx), ensuring project settings are loaded upon component mount and project changes.
  • New E2E Test: Added a comprehensive end-to-end test (board-background-persistence.spec.ts) to verify the persistence and correct loading of board background settings across project switches and app restarts, preventing future regressions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the background image loading issue by introducing a dedicated hook, useProjectSettingsLoader, and adding a comprehensive E2E test to prevent future regressions. The approach is solid. My review includes suggestions to improve the maintainability of the new hook by reducing code repetition and to enhance the robustness of the new tests by replacing fixed timeouts with more reliable event-based waits.

Comment on lines 40 to 73
if (result.success && result.settings?.boardBackground) {
const bg = result.settings.boardBackground;

// Update store with loaded settings (without triggering server save)
setBoardBackground(currentProject.path, bg.imagePath);

if (bg.cardOpacity !== undefined) {
setCardOpacity(currentProject.path, bg.cardOpacity);
}

if (bg.columnOpacity !== undefined) {
setColumnOpacity(currentProject.path, bg.columnOpacity);
}

if (bg.columnBorderEnabled !== undefined) {
setColumnBorderEnabled(currentProject.path, bg.columnBorderEnabled);
}

if (bg.cardGlassmorphism !== undefined) {
setCardGlassmorphism(currentProject.path, bg.cardGlassmorphism);
}

if (bg.cardBorderEnabled !== undefined) {
setCardBorderEnabled(currentProject.path, bg.cardBorderEnabled);
}

if (bg.cardBorderOpacity !== undefined) {
setCardBorderOpacity(currentProject.path, bg.cardBorderOpacity);
}

if (bg.hideScrollbar !== undefined) {
setHideScrollbar(currentProject.path, bg.hideScrollbar);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The series of if statements to update the store is quite repetitive. This can be refactored into a more concise and maintainable structure using a map of setting keys to their corresponding setter functions. This approach will make it easier to add or remove settings in the future, as you would only need to update the map.

        if (result.success && result.settings?.boardBackground) {
          const bg = result.settings.boardBackground;
          const projectPath = currentProject.path;

          // Update store with loaded settings (without triggering server save)
          setBoardBackground(projectPath, bg.imagePath);

          const settingsMap = {
            cardOpacity: setCardOpacity,
            columnOpacity: setColumnOpacity,
            columnBorderEnabled: setColumnBorderEnabled,
            cardGlassmorphism: setCardGlassmorphism,
            cardBorderEnabled: setCardBorderEnabled,
            cardBorderOpacity: setCardBorderOpacity,
            hideScrollbar: setHideScrollbar,
          } as const;

          for (const [key, setter] of Object.entries(settingsMap)) {
            const value = bg[key as keyof typeof bg];
            if (value !== undefined) {
              // The type assertion is safe because we constructed the map carefully.
              (setter as (path: string, val: typeof value) => void)(projectPath, value);
            }
          }
        }


// CRITICAL: Wait for settings to be loaded (useProjectSettingsLoader hook)
// This ensures the background settings are fetched from the server
await page.waitForTimeout(2000);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using page.waitForTimeout() can lead to flaky tests because it waits for a fixed amount of time, which may not be enough on a slow machine or too long on a fast one. It's more robust to wait for a specific event to occur. In this case, you can wait for the network request that loads the project settings to complete.

Suggested change
await page.waitForTimeout(2000);
await page.waitForResponse(resp =>
resp.url().includes('/api/settings/project') &&
resp.request().postData()?.includes(projectAPath)
);

).toBeVisible({ timeout: 5000 });

// CRITICAL: Wait for settings to be loaded again
await page.waitForTimeout(2000);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the previous wait, using page.waitForResponse() is more reliable than page.waitForTimeout(). This will ensure the settings have been reloaded after switching back to the project before the test proceeds, making the test less prone to race conditions.

    await page.waitForResponse(resp => 
      resp.url().includes('/api/settings/project') && 
      resp.request().postData()?.includes(projectAPath)
    );

await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 });

// Wait for settings to load
await page.waitForTimeout(2000);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To make this test more robust and avoid potential flakiness, it's better to wait for the specific network request to complete rather than using a fixed timeout. page.waitForResponse() can be used to wait for the settings to be loaded from the API.

    await page.waitForResponse(resp => 
      resp.url().includes('/api/settings/project') && 
      resp.request().postData()?.includes(projectPath)
    );

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

🤖 Fix all issues with AI Agents
In @apps/ui/src/hooks/use-project-settings-loader.ts:
- Line 40: The current check in useProjectSettingsLoader only continues when
result.success && result.settings?.boardBackground, which skips valid settings
objects that lack boardBackground; change the condition to check result.success
&& result.settings so settings are processed regardless of boardBackground
presence, and then handle boardBackground as an optional property (e.g., apply
it only if result.settings.boardBackground exists or use a default) while
keeping the rest of the settings application logic unchanged.
- Around line 81-91: The effect in use-project-settings-loader (the useEffect
that depends on currentProject?.path and many setters like setBoardBackground,
setCardOpacity, setColumnOpacity, setColumnBorderEnabled, setCardGlassmorphism,
setCardBorderEnabled, setCardBorderOpacity, setHideScrollbar) should only depend
on currentProject?.path; remove all the stable setter functions from the
dependency array and change it to only [currentProject?.path], keeping the
existing loadingRef guard and effect body unchanged so the effect triggers only
when the project path changes.

In @apps/ui/tests/projects/board-background-persistence.spec.ts:
- Line 252: The test is flaky because the hook uses loadingRef to prevent
duplicate loads, so change the assertion on projectASettingsCalls from
expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(2) to a more lenient
expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(1); alternatively,
if you want to assert the second load, update the test to simulate clearing the
hook's loadingRef by unmounting/remounting or explicitly switching projects so
the hook (loadingRef) is reset before switching back — locate the test variables
projectASettingsCalls and the hook's loadingRef usage to implement the change.
🧹 Nitpick comments (2)
apps/ui/tests/projects/board-background-persistence.spec.ts (2)

189-189: Replace hardcoded timeouts with deterministic waits.

The test uses multiple page.waitForTimeout() calls with arbitrary delays (2000ms, 500ms). This is an anti-pattern that makes tests slower and potentially flaky. Instead, wait for specific DOM conditions or API responses.

🔎 Recommended approach to replace timeouts

Instead of:

await page.waitForTimeout(2000);

Consider waiting for the API call to complete:

// Wait for settings API call to complete
const settingsLoadPromise = page.waitForRequest(
  request => request.url().includes('/api/settings/project') 
    && request.method() === 'POST'
    && (request.postData()?.includes(projectAPath) ?? false),
  { timeout: 5000 }
);
await settingsLoadPromise;

Or wait for a DOM element that indicates settings are loaded (if such an element exists):

// Wait for background to be applied (if there's a CSS property or data attribute you can check)
await expect(boardView).toHaveAttribute('data-background-loaded', 'true', { timeout: 5000 });

Also applies to: 238-238, 373-373


192-194: Missing verification of actual UI rendering.

The comment acknowledges that the test can't directly access React state and should verify via DOM/CSS, but then doesn't actually perform this verification. The test only confirms that API calls are made and the settings file has correct content, but doesn't verify that the background image is visually applied.

Consider adding a check for the background image in the DOM:

// Check if the background image is applied via inline style or CSS
const boardView = page.locator('[data-testid="board-view"]');
const backgroundStyle = await boardView.evaluate((el) => {
  return window.getComputedStyle(el).backgroundImage;
});
expect(backgroundStyle).toContain('background.jpg');
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe7bc95 and 46636cf.

⛔ Files ignored due to path filters (1)
  • apps/ui/tests/img/background.jpg is excluded by !**/*.jpg
📒 Files selected for processing (3)
  • apps/ui/src/hooks/use-project-settings-loader.ts
  • apps/ui/src/routes/__root.tsx
  • apps/ui/tests/projects/board-background-persistence.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/hooks/use-project-settings-loader.ts
  • apps/ui/tests/projects/board-background-persistence.spec.ts
  • apps/ui/src/routes/__root.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/hooks/use-project-settings-loader.ts
  • apps/ui/tests/projects/board-background-persistence.spec.ts
  • apps/ui/src/routes/__root.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always import from shared packages (automaker/*), never from old relative paths

Applied to files:

  • apps/ui/src/routes/__root.tsx
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/routes/__root.tsx
🧬 Code graph analysis (3)
apps/ui/src/hooks/use-project-settings-loader.ts (1)
apps/ui/src/lib/http-api-client.ts (1)
  • getHttpApiClient (1985-1990)
apps/ui/tests/projects/board-background-persistence.spec.ts (3)
apps/ui/tests/utils/git/worktree.ts (1)
  • createTempDirPath (52-55)
apps/ui/tests/utils/api/client.ts (1)
  • authenticateForTests (357-361)
apps/ui/tests/utils/core/interactions.ts (1)
  • handleLoginScreenIfPresent (65-115)
apps/ui/src/routes/__root.tsx (1)
apps/ui/src/hooks/use-project-settings-loader.ts (1)
  • useProjectSettingsLoader (10-92)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (2)
apps/ui/src/routes/__root.tsx (1)

27-27: LGTM! Hook integration is clean and well-placed.

The useProjectSettingsLoader hook is correctly imported and invoked within RootLayoutContent. The placement is appropriate since the hook has internal guards (!currentProject?.path) that prevent premature execution before the app is fully initialized.

Also applies to: 51-52

apps/ui/tests/projects/board-background-persistence.spec.ts (1)

41-266: Good test coverage for the loading mechanism.

The tests effectively verify that:

  1. Settings are loaded when switching between projects
  2. Settings are loaded on app restart with a current project
  3. API calls are made with the correct project paths
  4. Settings persist in the file system

This provides solid coverage for the useProjectSettingsLoader hook functionality, even though UI rendering verification is missing.

Also applies to: 268-398

console.log('Looking for path:', projectAPath);
}

expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(2);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test expectation may be flaky due to duplicate load prevention.

The test expects at least 2 API calls for project A (initial load + switch back), but the loadingRef in the hook prevents duplicate loads for the same project path. If the ref isn't cleared when switching away from project A, the second load might be skipped, causing this assertion to fail.

Consider:

  1. Verifying the hook clears the ref when the project changes (it doesn't currently)
  2. Adjusting the expectation to be more lenient: expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(1)
  3. Adding logic to track project switches and verify the behavior more precisely
🤖 Prompt for AI Agents
In @apps/ui/tests/projects/board-background-persistence.spec.ts at line 252, The
test is flaky because the hook uses loadingRef to prevent duplicate loads, so
change the assertion on projectASettingsCalls from
expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(2) to a more lenient
expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(1); alternatively,
if you want to assert the second load, update the test to simulate clearing the
hook's loadingRef by unmounting/remounting or explicitly switching projects so
the hook (loadingRef) is reset before switching back — locate the test variables
projectASettingsCalls and the hook's loadingRef usage to implement the change.

@webdevcody
Copy link
Collaborator

@claude address the pr comments

@AutoMaker-Org AutoMaker-Org deleted a comment from claude bot Jan 7, 2026
@webdevcody
Copy link
Collaborator

can you address the comments and see if they are valid

@yumesha
Copy link
Contributor Author

yumesha commented Jan 8, 2026

@webdevcody I have address the PR it all good now.

@webdevcody webdevcody merged commit 271749a into AutoMaker-Org:main Jan 8, 2026
5 of 6 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.

2 participants