Skip to content

Kanban scaling#211

Merged
webdevcody merged 7 commits intomainfrom
kanban-scaling
Dec 22, 2025
Merged

Kanban scaling#211
webdevcody merged 7 commits intomainfrom
kanban-scaling

Conversation

@trueheads
Copy link
Collaborator

@trueheads trueheads commented Dec 21, 2025

Summary by CodeRabbit

  • New Features

    • Window bounds persistence with on-screen validation and restore; dynamic minimum-width updates when the sidebar toggles
    • Kanban: improved responsive scaling and centered layout; expanded board actions/behaviors and shortcuts; prevents horizontal scrolling in desktop mode
  • Bug Fixes / Improvements

    • Drag-and-drop support for Create Markdown content and more robust file-import handling
  • Tests

    • Added responsive-kanban sizing tests; updated UI tests for the new Create Markdown flow and file-upload behavior
  • Chores

    • Added server-bundle package manifest and a persisted window-bounds setting; code style/quote standardization

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @trueheads, 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 more fluid and robust user experience for the Kanban board, particularly within the Electron desktop environment. It addresses layout challenges by implementing dynamic column scaling and intelligent window management, ensuring the application adapts seamlessly to various window sizes and user interactions. The changes also lay the groundwork for future server-side functionalities with the introduction of a new server bundle.

Highlights

  • Kanban Board Responsiveness: The Kanban board's layout has been significantly improved to be more responsive, dynamically adjusting column widths to fill available space and prevent horizontal scrolling, especially within the Electron application. This includes using useLayoutEffect for initial sizing and ResizeObserver for continuous updates.
  • Electron Window Management: The Electron main process now intelligently manages window sizing. It persists and restores the window's position, dimensions, and maximized state across sessions. Additionally, the minimum window width dynamically adapts based on the sidebar's expanded or collapsed state, ensuring the Kanban board always remains fully visible.
  • New Server Bundle: A new server bundle has been introduced, indicated by the addition of package.json and package-lock.json files in apps/app/server-bundle, bringing in new dependencies for the backend services.
  • Enhanced Testing: New Playwright tests have been added to specifically validate the responsive scaling behavior of the Kanban columns, covering scenarios like minimum window width and sidebar state changes.

🧠 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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a server-bundle package.json; persists, validates, and restores Electron window bounds; exposes an IPC to update minimum width and calls it from the sidebar auto-collapse hook; refactors responsive Kanban sizing (ResizeObserver, containerStyle, new metrics); expands KanbanBoard props; updates context/file tests and various UI styling/formatting.

Changes

Cohort / File(s) Summary
Server bundle setup
apps/app/server-bundle/package.json
New package.json adding server-bundle metadata, main entry, and runtime dependencies.
Window persistence & IPC
apps/ui/src/main.ts, libs/types/src/settings.ts
Adds WindowBounds type and windowBounds in settings; load/save debounced window bounds with on-screen validation; restores bounds/maximized state; adds kanban sizing constants and ipcMain.handle('window:updateMinWidth', ...).
Preload API
apps/ui/src/preload.ts
Exposes electronAPI.updateMinWidth(sidebarExpanded: boolean) and standardizes IPC channel strings.
Sidebar auto-collapse hook & sidebar
apps/ui/src/components/layout/sidebar/hooks/use-sidebar-auto-collapse.ts, apps/ui/src/components/layout/sidebar.tsx
Hook invokes electronAPI.updateMinWidth(sidebarOpen) when sidebarOpen changes; sidebar comment updated.
Responsive Kanban hook
apps/ui/src/hooks/use-responsive-kanban.ts
Hook accepts config param; adds useLayoutEffect, useRef, ResizeObserver; returns totalBoardWidth, isInitialized, and containerStyle; recalculates on sidebar transitions and resize.
Kanban board API & layout
apps/ui/src/components/views/board-view/kanban-board.tsx
Expands KanbanBoardProps with many callbacks and layout/background props; applies containerStyle; outer wrapper uses overflow-x-hidden and adjusted padding/centering.
Kanban column styling
apps/ui/src/components/views/board-view/components/kanban-column.tsx
ClassName updates and restricts animated properties to transition-[box-shadow,ring] duration-200; minor formatting changes.
Context view & drag/drop
apps/ui/src/components/views/context-view.tsx, apps/ui/tests/context-view.spec.ts, apps/ui/tests/utils/files/drag-drop.ts
Adds drag-and-drop handling to Create Markdown textarea; tests updated to new Create Markdown flow and upload selectors; test helper simulates drag/drop with explicit dataTransfer binding.
Responsive Kanban tests
apps/ui/tests/kanban-responsive-scaling.spec.ts
Standardizes quotes and adds tests verifying columns fit at 1500px and scale correctly when sidebar collapses without horizontal scrollbar.
Server tests & config
apps/server/vitest.config.ts, apps/server/tests/..., apps/server/tests/unit/providers/claude-provider.test.ts
Coverage exclude list expanded; tests updated to use CLAUDE_MODEL_MAP constants and new Claude model ids; formatting normalizations.
Types: window bounds
libs/types/src/settings.ts
Adds WindowBounds interface and optional windowBounds on GlobalSettings.
Misc: formatting & minor UI tweaks
various UI files and tests (quote normalization, small className tweaks)
Non-functional formatting and styling adjustments across multiple UI files and tests.

Sequence Diagram(s)

sequenceDiagram
    participant Sidebar as Sidebar (renderer)
    participant Hook as use-sidebar-auto-collapse (renderer)
    participant Preload as Preload (renderer bridge)
    participant Main as Electron Main
    participant Window as BrowserWindow

    Sidebar->>Hook: sidebarOpen changes
    Hook->>Preload: electronAPI.updateMinWidth(sidebarOpen)
    Preload->>Main: invoke 'window:updateMinWidth' with sidebarOpen
    Main->>Main: compute newMinWidth (kanban constants)
    Main->>Window: setMinimumSize(newMinWidth, minHeight)
    alt current width < newMinWidth
        Main->>Window: resize to newMinWidth
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • apps/ui/src/main.ts — bounds persistence, display validation, IPC handler, min-width resize behavior.
    • apps/ui/src/hooks/use-responsive-kanban.ts — ResizeObserver lifecycle, debounce, containerStyle math, isInitialized timing.
    • apps/ui/src/components/views/board-view/kanban-board.tsx — expanded props surface and wiring.
    • apps/ui/tests/kanban-responsive-scaling.spec.ts and apps/ui/tests/context-view.spec.ts — updated selectors and flow changes.

Possibly related PRs

Suggested labels

Refactor, Testers-Requested, Do Not Merge

Poem

🐰 I hopped along the window frame,

Poked the sidebar, changed its name,
Columns stretch and screens align,
Bounds saved snug — the board's divine,
Little rabbit claps, "All fine!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'Kanban scaling' directly reflects the main objective of the pull request, which is to rebuild and fix the Kanban scaling logic for both web and Electron environments.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kanban-scaling

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

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 introduces significant improvements to the Kanban board's responsiveness, particularly for the Electron desktop application. It adds robust logic for persisting window size and position, and dynamically adjusts the window's minimum width based on the sidebar's state to ensure a clean layout. The column sizing logic has been enhanced for better performance and accuracy. My review includes a high-severity comment about a potential regression in column width handling that also causes a test failure, and a medium-severity comment to correct some misleading code comments for better maintainability. Overall, this is a high-quality contribution that greatly improves the application's user experience.

Comment on lines +47 to +50
const BOARD_CONTENT_MIN =
COLUMN_MIN_WIDTH * COLUMN_COUNT + GAP_SIZE * (COLUMN_COUNT - 1) + BOARD_PADDING;
const MIN_WIDTH_EXPANDED = BOARD_CONTENT_MIN + SIDEBAR_EXPANDED; // 1500px
const MIN_WIDTH_COLLAPSED = BOARD_CONTENT_MIN + SIDEBAR_COLLAPSED; // 1276px
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 comments for the calculated minimum widths are slightly incorrect, which could be misleading for future maintenance.

  • BOARD_CONTENT_MIN is 280 * 4 + 20 * 3 + 40 = 1220.
  • MIN_WIDTH_EXPANDED is 1220 + 288 = 1508px (comment on line 49 says 1500px).
  • MIN_WIDTH_COLLAPSED is 1220 + 64 = 1284px (comment on line 50 says 1276px).

Similarly, the comment on line 406 for minWidth should be updated from 1500px to 1508px.

Please update these comments to reflect the correct values.

Suggested change
const BOARD_CONTENT_MIN =
COLUMN_MIN_WIDTH * COLUMN_COUNT + GAP_SIZE * (COLUMN_COUNT - 1) + BOARD_PADDING;
const MIN_WIDTH_EXPANDED = BOARD_CONTENT_MIN + SIDEBAR_EXPANDED; // 1500px
const MIN_WIDTH_COLLAPSED = BOARD_CONTENT_MIN + SIDEBAR_COLLAPSED; // 1276px
const BOARD_CONTENT_MIN =
COLUMN_MIN_WIDTH * COLUMN_COUNT + GAP_SIZE * (COLUMN_COUNT - 1) + BOARD_PADDING;
const MIN_WIDTH_EXPANDED = BOARD_CONTENT_MIN + SIDEBAR_EXPANDED; // 1508px
const MIN_WIDTH_COLLAPSED = BOARD_CONTENT_MIN + SIDEBAR_COLLAPSED; // 1284px

Copy link
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: 1

🧹 Nitpick comments (5)
apps/ui/src/components/layout/sidebar.tsx (1)

378-389: LGTM! Consider adding error handling for robustness.

The implementation correctly checks for the Electron API before calling updateMinWidth. However, since updateMinWidth returns a Promise, unhandled rejections could occur if the IPC call fails.

🔎 Optional: Add error handling
   useEffect(() => {
     const electronAPI = (
       window as unknown as {
         electronAPI?: { updateMinWidth?: (expanded: boolean) => Promise<void> };
       }
     ).electronAPI;
     if (electronAPI?.updateMinWidth) {
-      electronAPI.updateMinWidth(sidebarOpen);
+      electronAPI.updateMinWidth(sidebarOpen).catch((err) => {
+        console.warn('[Sidebar] Failed to update min width:', err);
+      });
     }
   }, [sidebarOpen]);
apps/ui/src/main.ts (1)

55-62: Consider importing WindowBounds from shared types.

This interface duplicates the WindowBounds type defined in libs/types/src/settings.ts. While the duplication works, importing from the shared types package would ensure consistency.

🔎 Optional: Import from shared types
+import type { WindowBounds } from '@automaker/types';
+
-// Window bounds interface (matches @automaker/types WindowBounds)
-interface WindowBounds {
-  x: number;
-  y: number;
-  width: number;
-  height: number;
-  isMaximized: boolean;
-}
apps/ui/src/hooks/use-responsive-kanban.ts (2)

57-93: DOM query in calculation function may return stale or null references.

The calculateColumnWidth function queries the DOM via document.querySelector on every call. If the element isn't mounted yet (e.g., during initial render or SSR), boardContainer could be null, falling back to window.innerWidth. This is handled, but the fallback may cause a brief incorrect width before the container mounts.

Also, using data-testid for functional DOM queries couples test infrastructure to runtime behavior. Consider using a ref passed from the component or a dedicated data-* attribute for this purpose.


131-144: ResizeObserver may not find the container on initial mount.

The DOM query runs once when the effect mounts. If [data-testid="board-view"] or its parent isn't in the DOM yet, container will be null and the observer won't be set up. The effect won't re-run unless calculateColumnWidth identity changes.

Consider adding the container element as a dependency or using a ref callback pattern to ensure the observer attaches when the element mounts.

🔎 Potential approach using a callback ref
// In the hook, expose a ref callback for the consumer to attach
const containerRef = useRef<HTMLElement | null>(null);
const setContainerRef = useCallback((node: HTMLElement | null) => {
  containerRef.current = node;
  if (node) {
    // Trigger recalculation when container is available
    setColumnWidth(calculateColumnWidth(node.clientWidth));
  }
}, [calculateColumnWidth]);

// In the ResizeObserver effect, use containerRef.current instead of DOM query

This would require the consumer (KanbanBoard) to attach the ref, but provides more reliable container tracking.

apps/ui/src/components/views/board-view/kanban-board.tsx (1)

14-52: Props interface is comprehensive but consider grouping related callbacks.

The interface has many callback props (onEdit, onDelete, onViewOutput, etc.). While this is functional, consider whether some of these could be grouped into a single handler object for better organization, e.g., featureActions: { edit, delete, viewOutput, ... }.

This is optional and may be out of scope for this PR.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee9ccd0 and 042fc61.

⛔ Files ignored due to path filters (1)
  • apps/app/server-bundle/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • apps/app/server-bundle/package.json (1 hunks)
  • apps/ui/src/components/layout/sidebar.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-column.tsx (3 hunks)
  • apps/ui/src/components/views/board-view/kanban-board.tsx (7 hunks)
  • apps/ui/src/hooks/use-responsive-kanban.ts (3 hunks)
  • apps/ui/src/main.ts (16 hunks)
  • apps/ui/src/preload.ts (1 hunks)
  • apps/ui/tests/kanban-responsive-scaling.spec.ts (6 hunks)
  • libs/types/src/settings.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/ui/src/components/views/board-view/kanban-board.tsx (1)
apps/ui/src/hooks/use-responsive-kanban.ts (1)
  • useResponsiveKanban (48-194)
apps/ui/tests/kanban-responsive-scaling.spec.ts (1)
apps/ui/tests/utils/git/worktree.ts (3)
  • createTempDirPath (52-55)
  • setupProjectWithPathNoWorktrees (362-404)
  • waitForBoardView (467-487)
apps/ui/src/hooks/use-responsive-kanban.ts (1)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (838-2265)
⏰ 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 (23)
apps/ui/src/preload.ts (1)

41-43: LGTM!

The new updateMinWidth API is correctly exposed, matching the IPC handler signature in main.ts. The method appropriately returns Promise<void> and uses a clear parameter name.

libs/types/src/settings.ts (2)

73-90: LGTM!

The WindowBounds interface is well-designed with clear JSDoc documentation. All required fields for window state persistence are included.


294-297: LGTM!

Making windowBounds optional is the correct approach since this is an Electron-only feature. The documentation clearly explains its purpose.

apps/ui/src/components/views/board-view/components/kanban-column.tsx (1)

41-46: LGTM!

Good optimization to narrow the transition to only box-shadow and ring properties. This prevents the width from animating during responsive resizing, which would cause visual jank. The comment clearly explains the rationale.

apps/ui/tests/kanban-responsive-scaling.spec.ts (2)

206-244: LGTM! Good test coverage for the minimum width constraint.

This test correctly validates that the kanban layout fits within the new Electron minimum width (1500px) without horizontal scrolling. The column width bounds (280-360px) align with the constants defined in main.ts.


246-292: LGTM! Thorough test for sidebar collapse behavior.

The test correctly handles the conditional visibility of the collapse button and verifies that:

  1. Columns scale appropriately when more space is available
  2. Width stays within expected bounds
  3. No horizontal scrollbar appears after collapse
apps/ui/src/main.ts (5)

34-53: LGTM! Well-documented sizing constants.

The calculations are clear and the comments explain how the values derive from the kanban layout requirements. The breakdown (columns, gaps, padding, sidebar) makes it easy to verify correctness.


137-162: LGTM! Good debounce implementation for window bounds persistence.

The 500ms debounce prevents excessive disk writes during window drag/resize operations. Using getNormalBounds() for maximized windows correctly captures the pre-maximized dimensions.


164-204: Solid display validation logic.

The implementation correctly:

  • Checks if the window center is visible on any display
  • Resets to primary display if completely off-screen
  • Constrains dimensions to fit within the display
  • Enforces minimum dimensions

442-469: LGTM! Proper window state persistence lifecycle.

The implementation correctly:

  • Saves immediately on close (not debounced) to capture final state
  • Uses debounced saves for resized and moved events
  • Handles both maximized and normal window states

620-631: LGTM! Dynamic minimum width adjustment.

The IPC handler correctly updates the window's minimum size based on sidebar state and resizes the window if it's below the new minimum. This ensures the kanban board always fits.

apps/app/server-bundle/package.json (1)

9-9: No action needed on dotenv version.

The specified version ^17.2.3 is correct—it is the latest version of dotenv. The original concern was based on outdated information; dotenv has progressed beyond the 16.x series.

apps/ui/src/hooks/use-responsive-kanban.ts (6)

1-2: LGTM!

Import changes are appropriate: useLayoutEffect for pre-paint calculations, useRef for mutable timeout reference, and useAppStore for sidebar state access.


15-24: Config changes look intentional, but verify Infinity behavior.

Setting columnMaxWidth: Infinity allows columns to scale without upper bound. This is fine for the clamping logic since Math.min(Infinity, x) = x, but ensure this is the intended behavior for all viewport sizes—very wide monitors will have very wide columns.


95-110: Good use of useLayoutEffect for synchronous measurement.

Using useLayoutEffect to calculate width before paint correctly prevents the visual "bounce" effect. The isInitialized state flag is a useful addition for consumers to know when the initial calculation is complete.


112-159: ResizeObserver setup is solid with proper cleanup.

The implementation correctly:

  • Creates and disconnects the observer in cleanup
  • Clears the debounce timeout on unmount
  • Falls back to window resize when ResizeObserver isn't available

One minor observation: the ResizeObserver callback updates state directly (not debounced), while the window resize fallback is debounced. This asymmetry is intentional since ResizeObserver is more efficient, but worth noting for consistency.


161-169: Sidebar transition timing uses magic number offset.

The + 50 offset added to SIDEBAR_TRANSITION_MS is a common pattern but can be fragile if the sidebar animation timing changes. Consider documenting why the offset exists or making it a named constant.

Additionally, this effect creates a new timeout on every sidebarOpen toggle, which is correct behavior for re-triggering after transitions.


174-185: totalBoardWidth calculation is consistent with column width logic.

The formula correctly computes the total width including gaps. The containerStyle uses justifyContent: 'center' to center columns, which works well with the flex layout.

apps/ui/src/components/views/board-view/kanban-board.tsx (5)

1-12: LGTM!

Import statements are well-organized. The quote normalization (to single quotes) appears to be consistent with codebase conventions.


85-97: Good integration of responsive hook with container styling.

The comment accurately describes the purpose. Using overflow-x-hidden on the outer container combined with the hook's containerStyle (which includes justifyContent: 'center') effectively prevents horizontal scrolling while centering the columns.

Note: overflow-x-hidden will clip any content that extends beyond the container bounds. Ensure this is acceptable for all column configurations.


112-158: Column-specific header actions are correctly implemented.

The conditional rendering for headerAction based on column.id is clear:

  • 'verified' column shows "Archive All" when features exist
  • 'backlog' column shows suggestions button and conditional "Make" button

The logic is straightforward and readable.


165-170: Shortcut key assignment logic is correct.

The calculation assigns keys '1'-'9' for indices 0-8 and '0' for index 9, providing keyboard shortcuts for the first 10 in-progress cards.


204-225: DragOverlay implementation looks good.

The easing function and drop animation configuration are appropriate for smooth drag-and-drop UX. The overlay card correctly uses columnWidth for consistent sizing.

@illia1f
Copy link
Contributor

illia1f commented Dec 22, 2025

Hey @trueheads, I noticed we both tackled the Kanban scaling issue. I submitted a targeted fix for #85 in #200 earlier and left a question. I see you've gone for a broader rebuild.

@trueheads
Copy link
Collaborator Author

Hey @illia1f, I had a chat with some of the other core contributors and decided to just rebuild the scaling logic pretty much entirely. This update will fix the scaling logic for both the electron window scaling, and the kanban scaling for web/electron. This should be a solid/stable approach that allows the app to also preserve session-to-session as well.

I did not choose to merge your PR because we were just throwing more code at an originally bad design (my own), so I took an hour and rebuilt/rethought how this should work.

I have not had time to fix the merge conflicts, but until I do, feel free to clone this branch and test it out.

@trueheads
Copy link
Collaborator Author

Screenshot 2025-12-21 164822 Screenshot 2025-12-21 164954

Resolves merge conflicts while preserving:
- Kanban scaling improvements (window sizing, bounce prevention, debouncing)
- Main's sidebar refactoring into hooks
- Main's openInEditor functionality for VS Code integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 1

🧹 Nitpick comments (1)
apps/ui/src/components/layout/sidebar/hooks/use-sidebar-auto-collapse.ts (1)

39-43: Consider simplifying the type assertion.

The inline type assertion for window.electronAPI is verbose. For better maintainability, consider declaring a global type definition or extracting this to a typed helper function.

🔎 Alternative approach with global type declaration

Create a type definition file (e.g., types/electron.d.ts):

declare global {
  interface Window {
    electronAPI?: {
      updateMinWidth?: (expanded: boolean) => Promise<void>;
      // ... other electron API methods
    };
  }
}

export {};

Then simplify the code to:

   useEffect(() => {
-    const electronAPI = (
-      window as unknown as {
-        electronAPI?: { updateMinWidth?: (expanded: boolean) => Promise<void> };
-      }
-    ).electronAPI;
+    const electronAPI = window.electronAPI;
     if (electronAPI?.updateMinWidth) {
       electronAPI.updateMinWidth(sidebarOpen);
     }
   }, [sidebarOpen]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd39f96 and 0cd3275.

📒 Files selected for processing (2)
  • apps/ui/src/components/layout/sidebar.tsx
  • apps/ui/src/components/layout/sidebar/hooks/use-sidebar-auto-collapse.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 (1)
apps/ui/src/components/layout/sidebar.tsx (1)

124-125: LGTM! Comment accurately reflects the extended behavior.

The updated comment correctly documents that useSidebarAutoCollapse now handles both auto-collapse on small screens and Electron window minWidth synchronization.

Comment on lines +36 to +47
// Update Electron window minWidth when sidebar state changes
// This ensures the window can't be resized smaller than what the kanban board needs
useEffect(() => {
const electronAPI = (
window as unknown as {
electronAPI?: { updateMinWidth?: (expanded: boolean) => Promise<void> };
}
).electronAPI;
if (electronAPI?.updateMinWidth) {
electronAPI.updateMinWidth(sidebarOpen);
}
}, [sidebarOpen]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for the async Electron API call.

The updateMinWidth call returns a promise that is not awaited and lacks error handling. If the IPC call fails or rejects, it will result in an unhandled promise rejection.

Consider adding a .catch() handler to log errors gracefully:

🔎 Proposed fix with error handling
   // Update Electron window minWidth when sidebar state changes
   // This ensures the window can't be resized smaller than what the kanban board needs
   useEffect(() => {
     const electronAPI = (
       window as unknown as {
         electronAPI?: { updateMinWidth?: (expanded: boolean) => Promise<void> };
       }
     ).electronAPI;
     if (electronAPI?.updateMinWidth) {
-      electronAPI.updateMinWidth(sidebarOpen);
+      electronAPI.updateMinWidth(sidebarOpen).catch((error) => {
+        console.error('[useSidebarAutoCollapse] Failed to update window minWidth:', error);
+      });
     }
   }, [sidebarOpen]);
🤖 Prompt for AI Agents
In apps/ui/src/components/layout/sidebar/hooks/use-sidebar-auto-collapse.ts
around lines 36 to 47, the call to the async Electron IPC method updateMinWidth
is not awaited and has no error handling, which can produce unhandled promise
rejections; wrap the call so rejections are handled — either invoke it inside an
async IIFE with try/catch or append .catch() to the returned promise and log the
error (use the app logger if available, otherwise console.error) to fail
gracefully.

Copy link
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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/server/tests/unit/lib/model-resolver.test.ts (1)

35-41: Incomplete refactoring: Line 37 should use CLAUDE_MODEL_MAP.opus.

While this file successfully refactors most Claude model references to use CLAUDE_MODEL_MAP (lines 27, 32, 53, 96, 100, 112), line 37 still contains the hardcoded string 'claude-opus-4-5-20251101'. This inconsistency creates a maintenance burden and contradicts the pattern established throughout the rest of the file.

🔎 Suggested fix to complete the refactoring
     it("should resolve 'opus' alias to full model string", () => {
       const result = resolveModelString('opus');
-      expect(result).toBe('claude-opus-4-5-20251101');
+      expect(result).toBe(CLAUDE_MODEL_MAP.opus);
       expect(consoleSpy.log).toHaveBeenCalledWith(
         expect.stringContaining('Resolved model alias: "opus"')
       );
     });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd3275 and 73cab38.

📒 Files selected for processing (3)
  • apps/server/tests/unit/lib/model-resolver.test.ts
  • apps/server/tests/unit/providers/claude-provider.test.ts
  • apps/ui/tests/context-view.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/server/tests/unit/lib/model-resolver.test.ts (1)
libs/types/src/index.ts (1)
  • CLAUDE_MODEL_MAP (36-36)
⏰ 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 (7)
apps/ui/tests/context-view.spec.ts (5)

49-67: LGTM!

The refactoring from the "Add File" flow to the dedicated "Create Markdown" flow is clean and consistent. Testids are properly updated, and the test logic correctly validates the new dialog lifecycle.


362-395: LGTM!

The drag-and-drop test is properly updated to use the new Create Markdown flow. The test correctly verifies that dropping a .md file auto-populates both the content textarea and filename field.


470-482: LGTM!

The duplicate filename test correctly uses the new Create Markdown flow and properly validates the overwrite behavior.


514-545: LGTM!

Both file creation sequences are properly updated to the new Create Markdown flow. The test effectively validates special character handling (parentheses, hyphens, underscores) in filenames.


576-586: LGTM!

The empty content edge case test is properly updated. The test correctly validates that files can be created with no content and that subsequent saves with empty content work as expected.

apps/server/tests/unit/providers/claude-provider.test.ts (1)

1-406: These test changes appear unrelated to the PR's stated objective.

This PR is titled "Kanban scaling" and described as a rebuild of Kanban scaling logic for Electron window and web behavior. However, this file contains only Claude AI model test updates, which have no apparent connection to Kanban UI scaling.

Please clarify:

  1. Are these changes intentionally included (e.g., from a merge)?
  2. Should these be in a separate PR focused on Claude model updates?
  3. If intentional, how do these model changes relate to Kanban scaling?
apps/server/tests/unit/lib/model-resolver.test.ts (1)

1-139: These test changes appear unrelated to the PR's stated objective.

This PR is titled "Kanban scaling" and aims to rebuild Kanban scaling logic for Electron and web. However, this file only refactors Claude AI model test references to use centralized constants—changes that have no connection to UI scaling or Kanban functionality.

Please clarify whether these changes were intentionally included or should be split into a separate PR focused on model constant refactoring.

Comment on lines +310 to 315
it('should include Claude Haiku 4.5', () => {
const models = provider.getAvailableModels();

const haiku = models.find((m) => m.id === 'claude-3-5-haiku-20241022');
const haiku = models.find((m) => m.id === 'claude-haiku-4-5-20251001');
expect(haiku).toBeDefined();
});
Copy link
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:

#!/bin/bash
# Search for CLAUDE_MODEL_MAP definition and usage
echo "=== Searching for CLAUDE_MODEL_MAP definition ==="
rg "CLAUDE_MODEL_MAP" --type ts -B 2 -A 5

echo -e "\n=== Checking for model-resolver.test.ts ==="
find . -name "model-resolver.test.ts" -type f

echo -e "\n=== Checking for claude-provider.test.ts ==="
find . -name "claude-provider.test.ts" -type f

Repository: AutoMaker-Org/automaker

Length of output: 24947


🏁 Script executed:

#!/bin/bash
echo "=== Checking claude-provider.test.ts around lines 310-315 ==="
sed -n '305,320p' apps/server/tests/unit/providers/claude-provider.test.ts

echo -e "\n=== Searching for hardcoded Claude model strings in claude-provider.test.ts ==="
rg "claude-(opus|sonnet|haiku)" apps/server/tests/unit/providers/claude-provider.test.ts -n

echo -e "\n=== Checking for CLAUDE_MODEL_MAP import in claude-provider.test.ts ==="
head -20 apps/server/tests/unit/providers/claude-provider.test.ts

Repository: AutoMaker-Org/automaker

Length of output: 1881


Use CLAUDE_MODEL_MAP for consistency across Claude provider tests.

This test hardcodes 'claude-haiku-4-5-20251001' while the related file model-resolver.test.ts in this same PR adopts CLAUDE_MODEL_MAP constants for all Claude model references, creating maintenance inconsistency. Additionally, line 298 uses an outdated Sonnet model version ('claude-sonnet-4-20250514') instead of the centralized constant.

Refactor lines 59, 71, 289, 298, 313, and 320 to import and use CLAUDE_MODEL_MAP from @automaker/types:

+import { CLAUDE_MODEL_MAP } from '@automaker/types';

Then replace hardcoded strings with:

  • CLAUDE_MODEL_MAP.opus (for 'claude-opus-4-5-20251101')
  • CLAUDE_MODEL_MAP.sonnet (for the outdated 'claude-sonnet-4-20250514' at line 298)
  • CLAUDE_MODEL_MAP.haiku (for 'claude-haiku-4-5-20251001')

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/server/tests/unit/providers/claude-provider.test.ts around lines 59, 71,
289, 298, 313 and 320, replace hardcoded Claude model ID strings with the
centralized CLAUDE_MODEL_MAP constants to keep tests consistent: add an import
of CLAUDE_MODEL_MAP from '@automaker/types' at the top of the file, then replace
occurrences as follows — use CLAUDE_MODEL_MAP.opus where the test currently uses
'claude-opus-4-5-20251101', use CLAUDE_MODEL_MAP.sonnet where it uses
'claude-sonnet-4-20250514' (line 298), and use CLAUDE_MODEL_MAP.haiku where it
uses 'claude-haiku-4-5-20251001' (lines 313/320); ensure all referenced test
assertions and finds use these constants instead of string literals.

Copy link
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: 1

🧹 Nitpick comments (1)
apps/ui/src/components/views/context-view.tsx (1)

1003-1029: Consider extracting the drag-and-drop handler.

The inline onDrop handler spans 23 lines, which makes the JSX less readable. Extracting it to a separate function would improve maintainability.

🔎 Suggested refactor

Define the handler before the return statement:

// Handle markdown file drop in Create Markdown dialog
const handleMarkdownDrop = useCallback(
  async (e: React.DragEvent<HTMLTextAreaElement>) => {
    e.preventDefault();
    e.stopPropagation();

    let files = Array.from(e.dataTransfer.files);
    if (files.length === 0 && e.dataTransfer.items) {
      const items = Array.from(e.dataTransfer.items);
      files = items
        .filter((item) => item.kind === 'file')
        .map((item) => item.getAsFile())
        .filter((f): f is globalThis.File => f !== null);
    }

    const mdFile = files.find((f) => isMarkdownFile(f.name));
    if (mdFile) {
      try {
        const content = await mdFile.text();
        setNewMarkdownContent(content);
        if (!newMarkdownName.trim()) {
          setNewMarkdownName(mdFile.name);
        }
      } catch (error) {
        console.error('Failed to read dropped file:', error);
        toast.error('Failed to read file', {
          description: 'The dropped file could not be read.',
        });
      }
    }
  },
  [isMarkdownFile, newMarkdownName]
);

Then in JSX:

               <textarea
                 id="markdown-content"
                 value={newMarkdownContent}
                 onChange={(e) => setNewMarkdownContent(e.target.value)}
-                onDrop={async (e) => { /* ... 23 lines ... */ }}
+                onDrop={handleMarkdownDrop}
                 onDragOver={(e) => {
                   e.preventDefault();
                   e.stopPropagation();
                 }}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc75923 and 4e876de.

📒 Files selected for processing (2)
  • apps/ui/src/components/views/context-view.tsx
  • apps/ui/tests/utils/files/drag-drop.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 (1)
apps/ui/tests/utils/files/drag-drop.ts (1)

22-44: LGTM! Excellent improvement for cross-browser compatibility.

The refactored approach using Object.defineProperty to explicitly define the dataTransfer property ensures it's accessible across all browsers. Some browsers don't properly expose dataTransfer when passed via the event constructor, so this pattern is a best practice for test utilities.

The added comment clearly documents the rationale, making future maintenance easier.

Comment on lines +1003 to +1025
onDrop={async (e) => {
e.preventDefault();
e.stopPropagation();

// Try files first, then items for better compatibility
let files = Array.from(e.dataTransfer.files);
if (files.length === 0 && e.dataTransfer.items) {
const items = Array.from(e.dataTransfer.items);
files = items
.filter((item) => item.kind === 'file')
.map((item) => item.getAsFile())
.filter((f): f is globalThis.File => f !== null);
}

const mdFile = files.find((f) => isMarkdownFile(f.name));
if (mdFile) {
const content = await mdFile.text();
setNewMarkdownContent(content);
if (!newMarkdownName.trim()) {
setNewMarkdownName(mdFile.name);
}
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for file reading.

The async mdFile.text() call on line 1019 can throw an error if the file is unreadable or corrupted. Currently, any error would silently fail and leave the user without feedback.

🔎 Proposed fix with error handling
                 const mdFile = files.find((f) => isMarkdownFile(f.name));
                 if (mdFile) {
-                  const content = await mdFile.text();
-                  setNewMarkdownContent(content);
-                  if (!newMarkdownName.trim()) {
-                    setNewMarkdownName(mdFile.name);
+                  try {
+                    const content = await mdFile.text();
+                    setNewMarkdownContent(content);
+                    if (!newMarkdownName.trim()) {
+                      setNewMarkdownName(mdFile.name);
+                    }
+                  } catch (error) {
+                    console.error('Failed to read dropped file:', error);
+                    toast.error('Failed to read file', {
+                      description: 'The dropped file could not be read.',
+                    });
                   }
                 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/components/views/context-view.tsx around lines 1003 to 1025, the
async mdFile.text() call can throw if the file is unreadable; wrap the file read
in a try/catch, log the error (console.error) and surface user feedback (e.g.,
set an error state or call an existing toast/notification helper) so the UI
informs the user the file could not be read; only setNewMarkdownContent and
setNewMarkdownName when reading succeeds, and keep existing behavior for naming
fallback.

@webdevcody webdevcody merged commit a85dec6 into main Dec 22, 2025
7 checks passed
@webdevcody webdevcody deleted the kanban-scaling branch December 22, 2025 14:30
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.

3 participants