Skip to content

Conversation

@shinokada
Copy link
Collaborator

@shinokada shinokada commented Oct 28, 2025

📑 Description

A fully responsive, drag-and-drop Kanban board component built with Svelte 5 and Tailwind CSS v4. Features vertical stacking on mobile, grid layout on larger screens, customizable column colors, card tags, and accessible ARIA labels. Styles are centralized using tailwind-variants for easy theming and maintainability.

Status

  • Not Completed
  • Completed

✅ Checks

  • My pull request adheres to the code style of this project
  • My code requires changes to the documentation
  • I have updated the documentation and api-check directory as required
  • All the tests and check have passed by running pnpm check && pnpm test:e2e
  • My pull request is based on the latest commit (not the npm version).
  • I have checked the page with https://validator.unl.edu/

ℹ Additional Information

Summary by CodeRabbit

  • New Features

    • Added a Kanban board with drag-and-drop, per-column add-card actions, card descriptions, tags, theming, and demo pages (default, custom styling, localStorage, API-backed).
  • Tests

    • Added end-to-end tests for KanbanBoard, StepIndicator, Tags, and VirtualList; removed an obsolete ButtonToggle test.
  • Documentation

    • Added Kanban setup, usage guide, examples, and an API integration demo with mock endpoints.
  • Style

    • Narrowed max line length in formatting config.

@vercel
Copy link

vercel bot commented Oct 28, 2025

@shinokada is attempting to deploy a commit to the Themesberg Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

Adds a Kanban feature: new Svelte components (KanbanBoard, KanbanCard) with drag-and-drop, theming and types, public re-exports, docs/examples and API mock routes, plus updated e2e tests replacing the ButtonToggle test with Kanban and other page-header checks.

Changes

Cohort / File(s) Summary
Kanban components
src/lib/kanban/KanbanBoard.svelte, src/lib/kanban/KanbanCard.svelte
New Svelte components: KanbanBoard implements column drop zones, drag state, ARIA roles, per-column add button and drag lifecycle; KanbanCard renders draggable card UI with title, optional description and tags, and drag event wiring.
Kanban module exports
src/lib/kanban/index.ts, src/lib/index.ts
New re-exports to expose KanbanBoard, KanbanCard, and kanban theme items from the library public API.
Kanban theming
src/lib/kanban/theme.ts, src/lib/theme/themes.ts
Added tailwind-variants configurations kanbanBoard and kanbanCard with slots and state variants (isDragOver, isDragging) and re-exported them from central themes.
Types
src/lib/types.ts
Added Kanban-related types and props: KanbanCardType, KanbanColumnType, KanbanBoardProps, KanbanCardProps, and imports for Kanban variant types.
Docs & examples
src/routes/docs-examples/extend/kanban-board/Default.svelte, .../CustomStyling.svelte, .../LocalStorage.svelte, .../ApiIntegration.svelte, src/routes/docs/extend/kanban-board.md
New example components and documentation demonstrating default board, custom styling, localStorage persistence, and API integration with sample handlers for onMove and onAddCard.
API mock routes
src/routes/api/kanban/mockData.ts, src/routes/api/kanban/columns/+server.ts, src/routes/api/kanban/move/+server.ts, src/routes/api/kanban/cards/+server.ts
New mock data and server endpoints: GET columns, POST move card, POST create card; include simulated latency, basic validation, and in-memory mockColumns mutation.
E2E tests
e2e/extend.spec.ts
Replaced the ButtonToggle test block with new page-header assertions for KanbanBoard and added tests for StepIndicator, Tags, and VirtualList pages.
Prettier config
.prettierrc
Adjusted printWidth from 1000 to 200.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Card as KanbanCard
    participant Board as KanbanBoard
    participant App as Parent

    Note over User,Board: Drag-and-drop a card between columns
    User->>Card: dragstart(card)
    Card->>Board: onDragStart(card)
    User->>Board: dragover(targetColumn)
    Board-->>User: preventDefault (allow drop)
    Board->>Board: set dragOverColumnId
    User->>Board: drop(on targetColumn)
    Board->>Board: remove card from sourceColumn.cards
    Board->>Board: insert card into targetColumn.cards
    Board->>App: onMove(card, from, to)
    App->>App: update columns (reactive reassign)
    Board->>Card: clear drag state
Loading
sequenceDiagram
    actor User
    participant Board as KanbanBoard
    participant App as Parent

    Note over User,Board: Add card flow
    User->>Board: click Add card (column)
    Board->>App: onAddCard(column)
    App->>App: append new card and reassign columns
    App->>Board: updated columns -> render new card
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to drag lifecycle and state cleanup in src/lib/kanban/KanbanBoard.svelte.
  • Verify ARIA attributes and drag event signatures vs. src/lib/types.ts.
  • Confirm theme slot names and variant usage in src/lib/kanban/theme.ts align with component class composition.
  • Review API handlers in src/routes/api/kanban/* for validation and in-memory mutation semantics.

Possibly related PRs

  • fix: types clean up #1609 — Modifies src/lib/types.ts; likely related to the new Kanban type additions and type-refactor interactions.

Poem

🐰 I hop where little task-cards scurry bright,
I nudge them gently from left to right.
Tiny tags like carrots, lanes tidy and clear,
A click, a drag — new order appears near.
Hooray for boards that help the work feel light.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title Check ✅ Passed The pull request title "feat: kanban board" clearly and concisely describes the primary change in the changeset. It follows conventional commit format and accurately reflects the main objective of adding a new Kanban board component with all its supporting infrastructure (components, types, theming, documentation, and examples). The title is specific enough that a developer scanning the history would immediately understand the purpose of this PR without ambiguity.
Description Check ✅ Passed The pull request description is mostly complete and follows the template structure. It includes a well-detailed 📑 Description section that explains the Kanban board component's features (responsive design, drag-and-drop, customizable colors, ARIA labels, and tailwind-variants theming). The Status section is properly filled with "Completed" checked, and the ✅ Checks section is comprehensively completed with only the validator.unl.edu check unchecked, which is optional. However, the description does not include a "Closes #" issue reference and the ℹ Additional Information section is empty, though these are not critical sections for understanding the change.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🧹 Nitpick comments (3)
src/routes/docs-examples/extend/kanban-board/Default.svelte (2)

69-78: Clarify the reactivity pattern.

The comment at line 72 states "The KanbanBoard component already mutated the columns," but then line 74 creates a new reference with columns = [...columns] to trigger reactivity. This pattern suggests the component directly mutates the bound prop.

Consider clarifying the expected data flow:

  • If the component mutates the prop directly, document this behavior and explain why the shallow copy is needed for reactivity
  • Alternatively, consider having the component return the new state without mutation, allowing the parent to handle the update

This is particularly important since Svelte 5's fine-grained reactivity typically doesn't require such patterns for deeply nested state.


80-98: Demo limitations are acceptable.

The use of prompt() for user input (line 81) and Date.now() for ID generation (line 85) are appropriate for a demonstration. However, consider adding a comment noting these are simplified for demo purposes:

 function handleAddCard(col: KanbanColumn) {
+  // Note: Using prompt() for demo - use proper form UI in production
   const cardTitle = prompt(`Add a new card to "${col.title}":`);
   if (!cardTitle?.trim()) return;

   const newCard: KanbanCard = {
+    // Note: Using timestamp for demo - use proper ID generation in production
     id: Date.now(),
     title: cardTitle.trim(),
     tags: ['new']
   };
src/lib/kanban/KanbanBoard.svelte (1)

1-119: Excellent accessibility and component structure.

The component demonstrates strong accessibility practices:

  • Semantic ARIA roles (group, list, listitem) for screen readers
  • Descriptive aria-label attributes on interactive zones
  • Proper keyboard interaction support with draggable
  • Clean separation of drag state management and rendering logic
  • Consistent theme integration using tailwind-variants

Optional improvement: Consider extracting card rendering into a separate component to improve maintainability:

<!-- KanbanCard.svelte -->
<script lang="ts">
  import type { KanbanCard } from "$lib/types";
  
  let {
    card,
    isDragging = false,
    onDragStart,
    onDragEnd,
    styles,
    theme,
    classes
  }: { /* props */ } = $props();
</script>

<article
  role="listitem"
  draggable="true"
  ondragstart={onDragStart}
  ondragend={onDragEnd}
  class={styles.card({ isDragging, class: clsx(theme?.card, classes?.card) })}
>
  <!-- card content -->
</article>

This would reduce the complexity of KanbanBoard.svelte and make card styling easier to customize.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 554a214 and 94a994a.

📒 Files selected for processing (9)
  • e2e/extend.spec.ts (1 hunks)
  • src/lib/index.ts (1 hunks)
  • src/lib/kanban/KanbanBoard.svelte (1 hunks)
  • src/lib/kanban/index.ts (1 hunks)
  • src/lib/kanban/theme.ts (1 hunks)
  • src/lib/theme/themes.ts (1 hunks)
  • src/lib/types.ts (2 hunks)
  • src/routes/docs-examples/extend/kanban-board/Default.svelte (1 hunks)
  • src/routes/docs/extend/kanban-board.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/lib/kanban/theme.ts (1)
src/lib/theme/themeUtils.ts (1)
  • Classes (12-14)
src/lib/types.ts (1)
src/lib/kanban/theme.ts (1)
  • KanbanBoardVariants (4-4)
⏰ 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: Playwright and Lint
🔇 Additional comments (10)
src/lib/kanban/theme.ts (1)

1-31: LGTM!

The theme configuration is well-structured with comprehensive responsive design patterns and appropriate state variants for drag-and-drop interactions.

src/lib/theme/themes.ts (1)

82-82: LGTM!

The export is correctly placed in the extend section and follows the established pattern.

src/lib/index.ts (1)

91-91: LGTM!

The export correctly extends the public API and follows the established wildcard export pattern.

e2e/extend.spec.ts (1)

3-43: LGTM!

The test suite properly covers all extend pages including the new KanbanBoard page. All tests follow the consistent pattern of verifying the h1 heading.

src/lib/kanban/index.ts (1)

1-2: LGTM!

The exports follow the library's established pattern for component modules.

src/routes/docs/extend/kanban-board.md (1)

1-31: LGTM!

The documentation is well-structured and provides clear usage instructions with a practical example.

src/routes/docs-examples/extend/kanban-board/Default.svelte (1)

101-120: LGTM!

The template is well-structured with appropriate responsive design and a helpful stats section that demonstrates accessing the column data.

src/lib/types.ts (2)

87-87: LGTM!

The import is correctly placed alongside other variant imports.


2060-2080: LGTM!

The Kanban type definitions are well-structured and comprehensive:

  • KanbanCard and KanbanColumn interfaces properly model the data structure
  • KanbanBoardProps correctly extends both KanbanBoardVariants and HTMLAttributes<HTMLDivElement>
  • Optional callbacks (onMove, onAddCard) provide flexibility
  • Type safety is maintained with proper generic constraints
src/lib/kanban/KanbanBoard.svelte (1)

35-57: Well-implemented drag-and-drop logic.

The handleDrop function correctly:

  • Performs defensive null checks and early returns
  • Prevents no-op moves within the same column
  • Validates source/target column existence
  • Mutates arrays using filter/spread to create new references (proper Svelte 5 reactivity pattern)
  • Propagates changes through $bindable columns prop
  • Invokes the onMove callback with correct arguments
  • Cleans up drag state

fix: move restProps spread to the container element
fix: use CSS custom properties
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: 2

🧹 Nitpick comments (2)
src/lib/types.ts (1)

2075-2088: Optionally include DragEvent in drag callbacks for flexibility.

Allows consumers to access dataTransfer and modifier keys without breaking existing usage.

Apply:

 export interface KanbanBoardProps extends KanbanBoardVariants, HTMLAttributes<HTMLDivElement> {
   columns?: KanbanColumnType[];
   onMove?: (card: KanbanCardType, from: KanbanColumnType, to: KanbanColumnType) => void;
   onAddCard?: (col: KanbanColumnType) => void;
   class?: ClassValue | null;
 }

 export interface KanbanCardProps extends KanbanCardVariants, HTMLAttributes<HTMLElement> {
   card: KanbanCardType;
   isDragging?: boolean;
-  onDragStart?: (card: KanbanCardType) => void;
-  onDragEnd?: () => void;
+  onDragStart?: (card: KanbanCardType, ev?: DragEvent) => void;
+  onDragEnd?: (ev?: DragEvent) => void;
   class?: ClassValue | null;
 }
src/lib/kanban/KanbanCard.svelte (1)

20-27: Add keyboard focus and ARIA drag state for accessibility. Board-level list structure is properly set up in KanbanBoard.svelte (role="list" with aria-label). Apply the suggested attributes to KanbanCard.svelte to make cards focusable and expose drag state:

 <article
   role="listitem"
   draggable="true"
   {...restProps}
   ondragstart={() => onDragStart?.(card)}
   ondragend={() => onDragEnd?.()}
+  tabindex="0"
+  aria-grabbed={isDragging}
+  aria-label={card.title}
   class={styles.card({ isDragging, class: clsx(theme?.card, classes?.card) })}
 >

Optionally add keyboard support (Enter/Space to start drag) and update types in src/lib/types.ts if you do:

-  ondragstart={() => onDragStart?.(card)}
+  ondragstart={(e) => onDragStart?.(card, e)}
+  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') onDragStart?.(card, e as unknown as DragEvent); }}
-  ondragend={() => onDragEnd?.()}
+  ondragend={(e) => onDragEnd?.(e)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92a2f9e and ce7ba04.

📒 Files selected for processing (7)
  • src/lib/kanban/KanbanBoard.svelte (1 hunks)
  • src/lib/kanban/KanbanCard.svelte (1 hunks)
  • src/lib/kanban/index.ts (1 hunks)
  • src/lib/kanban/theme.ts (1 hunks)
  • src/lib/theme/themes.ts (1 hunks)
  • src/lib/types.ts (2 hunks)
  • src/routes/docs-examples/extend/kanban-board/Default.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/theme/themes.ts
  • src/lib/kanban/KanbanBoard.svelte
  • src/lib/kanban/index.ts
  • src/lib/kanban/theme.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/types.ts (1)
src/lib/kanban/theme.ts (2)
  • KanbanBoardVariants (4-4)
  • KanbanCardVariants (5-5)
🪛 ESLint
src/lib/kanban/KanbanCard.svelte

[error] 40-44: Each block should have a key

(svelte/require-each-key)

src/routes/docs-examples/extend/kanban-board/Default.svelte

[error] 114-119: Each block should have a key

(svelte/require-each-key)

⏰ 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: Playwright and Lint
🔇 Additional comments (2)
src/routes/docs-examples/extend/kanban-board/Default.svelte (1)

69-78: Reactivity nudge looks good.

Reassigning columns to a new reference post-mutation is fine with $state.

src/lib/types.ts (1)

2061-2073: Kanban core types look solid.

Card/Column shapes are minimal and practical; optional color fits theming.

fix: add keyboard focus and ARIA drag state for accessibility
fix: add a key to the {#each} block to satisfy svelte/require-each-key to silence ESLint and avoid diff thrash
fix: key the {#each} over columns to improve DOM diffing
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce7ba04 and d8aa2ef.

📒 Files selected for processing (3)
  • src/lib/kanban/KanbanCard.svelte (1 hunks)
  • src/lib/types.ts (2 hunks)
  • src/routes/docs-examples/extend/kanban-board/Default.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/docs-examples/extend/kanban-board/Default.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/types.ts (1)
src/lib/kanban/theme.ts (2)
  • KanbanBoardVariants (4-4)
  • KanbanCardVariants (5-5)
⏰ 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: Playwright and Lint
🔇 Additional comments (3)
src/lib/kanban/KanbanCard.svelte (1)

32-50: Card rendering logic looks good.

The conditional rendering for description and tags, along with the theme-aware styling, is well implemented. Using the index as the key for tags (line 44) is acceptable for this use case since tags are simple strings.

src/lib/types.ts (2)

87-87: Import statement looks good.

The import of KanbanBoardVariants and KanbanCardVariants from the theme module is correct and follows the established pattern in this file.


2060-2088: Type definitions are well-structured.

The Kanban type definitions follow the codebase conventions and provide a clear, type-safe API. The interfaces properly extend variant props and HTML attributes, and the callback signatures appropriately include optional DragEvent parameters for accessing event details.

Comment on lines 25 to 26
ondragstart={() => onDragStart?.(card)}
ondragend={() => onDragEnd?.()}
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

Pass DragEvent to drag callbacks to match type signature.

The drag event handlers don't pass the DragEvent to the callbacks, but the type signature in KanbanCardProps (src/lib/types.ts lines 2085-2086) expects optional DragEvent parameters. This prevents consumers from accessing event details like dataTransfer or preventing default behavior.

Apply this diff to pass the events:

-  ondragstart={() => onDragStart?.(card)}
-  ondragend={() => onDragEnd?.()}
+  ondragstart={(ev) => onDragStart?.(card, ev)}
+  ondragend={(ev) => onDragEnd?.(ev)}
📝 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
ondragstart={() => onDragStart?.(card)}
ondragend={() => onDragEnd?.()}
ondragstart={(ev) => onDragStart?.(card, ev)}
ondragend={(ev) => onDragEnd?.(ev)}
🤖 Prompt for AI Agents
In src/lib/kanban/KanbanCard.svelte around lines 25-26, the drag handlers
currently call onDragStart?.(card) and onDragEnd?.() but don't forward the
DragEvent; update the handlers to forward the event so they match the
KanbanCardProps signature by changing them to call onDragStart?.(card, event)
and onDragEnd?.(card, event) (i.e., ondragstart={(e) => onDragStart?.(card, e)}
and ondragend={(e) => onDragEnd?.(card, e)}).

fix: pass DragEvent to drag callbacks to match type signature
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: 0

🧹 Nitpick comments (3)
src/lib/kanban/KanbanBoard.svelte (3)

8-8: Consider splitting the props destructuring for readability.

The props line exceeds 200 characters, making it difficult to scan. Breaking it into multiple lines would improve maintainability.

Example refactor:

-  let { columns = $bindable([]), onMove = (_card: KanbanCardType, _from: KanbanColumnType, _to: KanbanColumnType) => {}, onAddCard = (_col: KanbanColumnType) => {}, class: className, classes, ...restProps }: KanbanBoardProps = $props();
+  let {
+    columns = $bindable([]),
+    onMove = (_card: KanbanCardType, _from: KanbanColumnType, _to: KanbanColumnType) => {},
+    onAddCard = (_col: KanbanColumnType) => {},
+    class: className,
+    classes,
+    ...restProps
+  }: KanbanBoardProps = $props();

38-60: Consider reassigning the columns array for clearer reactivity.

The drop logic correctly creates new array references for fromCol.cards and toCol.cards, which should work with Svelte 5's $bindable. However, reassigning the entire columns array would make the reactivity pattern more explicit and predictable.

Optional refactor:

     const fromCol = columns.find((c) => c.id === sourceColumnId);
     const toCol = columns.find((c) => c.id === targetColId);
     if (!fromCol || !toCol) return;
 
-    fromCol.cards = fromCol.cards.filter((c) => c.id !== draggedCard!.id);
-    toCol.cards = [...toCol.cards, draggedCard!];
+    columns = columns.map((col) => {
+      if (col.id === sourceColumnId) {
+        return { ...col, cards: col.cards.filter((c) => c.id !== draggedCard!.id) };
+      }
+      if (col.id === targetColId) {
+        return { ...col, cards: [...col.cards, draggedCard!] };
+      }
+      return col;
+    });
 
     onMove(draggedCard, fromCol, toCol);

71-71: Split the column div attributes for readability.

Line 71 exceeds 400 characters, combining role, ARIA labels, styling, event handlers, and inline styles. This makes the code difficult to maintain and review.

Consider extracting the inline style or splitting the attributes:

-    <div role="group" aria-label={`${col.title} column drop zone`} class={styles.column({ isDragOver: dragOverColumnId === col.id, class: clsx(theme?.column, classes?.column) })} ondragover={(e) => handleDragOver(e, col.id)} ondragleave={(e) => handleDragLeave(e)} ondrop={(e) => handleDrop(e, col.id)} style={col.color ? `border-top: 4px solid ${col.color}` : ''}>
+    <div
+      role="group"
+      aria-label={`${col.title} column drop zone`}
+      class={styles.column({
+        isDragOver: dragOverColumnId === col.id,
+        class: clsx(theme?.column, classes?.column)
+      })}
+      ondragover={(e) => handleDragOver(e, col.id)}
+      ondragleave={(e) => handleDragLeave(e)}
+      ondrop={(e) => handleDrop(e, col.id)}
+      style={col.color ? `border-top: 4px solid ${col.color}` : ''}
+    >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8aa2ef and 0a0f38e.

📒 Files selected for processing (2)
  • src/lib/kanban/KanbanBoard.svelte (1 hunks)
  • src/lib/kanban/KanbanCard.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/kanban/KanbanCard.svelte
⏰ 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: Playwright and Lint
🔇 Additional comments (6)
src/lib/kanban/KanbanBoard.svelte (6)

10-17: LGTM!

Theme and state initialization is clean and properly typed for Svelte 5.


19-23: LGTM!

The drag start handler correctly captures the dragged card and source column.


25-28: LGTM!

The drag-over handler correctly prevents default behavior and tracks the target column for visual feedback.


30-36: LGTM!

The drag-leave handler correctly prevents false triggers by checking whether the related target is outside the column container.


62-66: LGTM!

The drag-end handler properly resets all drag-related state.


69-83: LGTM!

The template structure is well-organized with proper ARIA attributes for accessibility, keyed loops for efficient updates, and correct integration with the drag-and-drop handlers.

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

🧹 Nitpick comments (5)
src/routes/api/kanban/move/+server.ts (1)

7-7: Add input validation for request body.

The endpoint doesn't validate the incoming request body. Missing or invalid fields could cause runtime errors.

Consider adding validation:

 export const POST: RequestHandler = async ({ request }) => {
   const { cardId, fromColumnId, toColumnId } = await request.json();
+  
+  if (cardId == null || !fromColumnId || !toColumnId) {
+    return json({ error: 'Missing required fields' }, { status: 400 });
+  }
src/routes/api/kanban/cards/+server.ts (1)

7-7: Add input validation for title.

The title field is not validated. Empty or whitespace-only titles could create invalid cards.

Add validation:

 export const POST: RequestHandler = async ({ request }) => {
   const { title, columnId } = await request.json();
+  
+  if (!title?.trim() || !columnId) {
+    return json({ error: 'Missing required fields' }, { status: 400 });
+  }
src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1)

21-23: Clarify the "optimistic update" comment.

The comment states "Optimistic update" but the code columns = [...columns] only creates a shallow copy without actually moving the card in the local state. The actual card move is handled by the KanbanBoard component via the bind:columns binding before handleMove is called.

Consider updating the comment to reflect this:

   async function handleMove(card: KanbanCardType, from: KanbanColumnType, to: KanbanColumnType) {
-    // Optimistic update
+    // Trigger reactivity (card already moved by KanbanBoard component)
     columns = [...columns];
src/routes/docs-examples/extend/kanban-board/LocalStorage.svelte (1)

30-33: handleMove relies on KanbanBoard component mutation.

Similar to the ApiIntegration example, this handler expects the KanbanBoard component to mutate the columns binding before calling onMove. The reassignment columns = [...columns] triggers reactivity and the $effect saves to localStorage.

This pattern is correct but could benefit from a comment for clarity:

   function handleMove(card: KanbanCardType, from: KanbanColumnType, to: KanbanColumnType) {
     console.log(`Moved "${card.title}" from "${from.title}" to "${to.title}"`);
-    columns = [...columns]; // Trigger reactivity
+    columns = [...columns]; // Trigger reactivity and persistence (card already moved by component)
   }
src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1)

33-35: Improve input validation for card title.

The validation if (!title) doesn't catch whitespace-only input. A user could enter spaces and create a card with an invisible title.

Use consistent validation like the other examples:

   function handleAddCard(col: KanbanColumnType) {
     const title = prompt(`Add card to ${col.title}:`);
-    if (!title) return;
+    if (!title?.trim()) return;
     
     columns = columns.map((column) =>
       column.id === col.id
-        ? { ...column, cards: [...column.cards, { id: Date.now(), title }] }
+        ? { ...column, cards: [...column.cards, { id: Date.now(), title: title.trim() }] }
         : column
     );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0f38e and 76b7bf8.

📒 Files selected for processing (9)
  • .prettierrc (1 hunks)
  • src/routes/api/kanban/cards/+server.ts (1 hunks)
  • src/routes/api/kanban/columns/+server.ts (1 hunks)
  • src/routes/api/kanban/mockData.ts (1 hunks)
  • src/routes/api/kanban/move/+server.ts (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/LocalStorage.svelte (1 hunks)
  • src/routes/docs/extend/kanban-board.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/routes/docs/extend/kanban-board.md
🧰 Additional context used
🧬 Code graph analysis (3)
src/routes/api/kanban/columns/+server.ts (1)
src/routes/api/kanban/mockData.ts (1)
  • mockColumns (2-26)
src/routes/api/kanban/cards/+server.ts (2)
src/routes/api/kanban/move/+server.ts (1)
  • POST (6-29)
src/routes/api/kanban/mockData.ts (1)
  • mockColumns (2-26)
src/routes/api/kanban/move/+server.ts (2)
src/routes/api/kanban/cards/+server.ts (1)
  • POST (6-26)
src/routes/api/kanban/mockData.ts (1)
  • mockColumns (2-26)
🪛 ESLint
src/routes/api/kanban/mockData.ts

[error] 2-2: 'mockColumns' is never reassigned. Use 'const' instead.

(prefer-const)

⏰ 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: Playwright and Lint
🔇 Additional comments (5)
.prettierrc (1)

6-6: LGTM: Reasonable printWidth value.

Changing from 1000 to 200 is a sensible adjustment that will improve code readability while still allowing for reasonable line lengths.

src/routes/api/kanban/columns/+server.ts (1)

6-10: LGTM: Clean implementation.

The endpoint implementation is straightforward and appropriate for a demo/mock API. The 500ms delay effectively simulates network latency.

src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1)

38-42: Verify error handling reloads correct state.

The error handling reloads the board from the server, but there's no error handling for this reload fetch. If the reload fails, the board could be left in an inconsistent state.

Consider adding error handling for the reload:

     } catch (e) {
       // Rollback on error
       alert('Failed to move card. Please try again.');
       // Reload from server
-      const response = await fetch('/api/kanban/columns');
-      columns = await response.json();
+      try {
+        const response = await fetch('/api/kanban/columns');
+        if (response.ok) {
+          columns = await response.json();
+        }
+      } catch (reloadError) {
+        console.error('Failed to reload board:', reloadError);
+      }
     }
src/routes/docs-examples/extend/kanban-board/LocalStorage.svelte (1)

26-28: LGTM: Clean localStorage persistence.

The $effect reactively saves to localStorage whenever columns change. This provides automatic persistence without manual save calls.

src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1)

45-55: LGTM: Clean custom styling implementation.

The custom classes demonstrate how to override default Kanban board styling effectively. The classes prop provides good flexibility for theming.

tags: ['new']
};

column.cards.push(newCard);
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

Race condition: shared mutable state.

Similar to the move endpoint, this mutates mockColumns without synchronization, creating potential race conditions with concurrent requests. Acceptable for demo purposes but not production-ready.

🤖 Prompt for AI Agents
In src/routes/api/kanban/cards/+server.ts around line 23, the code mutates
shared in-memory state by doing column.cards.push(newCard), which can cause race
conditions under concurrent requests; instead, avoid mutating mockColumns
in-place: create a new column object with a new cards array that includes the
existing cards plus newCard, produce a new mockColumns array that replaces the
modified column (immutably), and assign that back in a single operation OR
protect the mutation with a proper synchronization mechanism (e.g., an async
mutex) or persist the change to a transactional datastore for production;
implement one of these approaches and remove direct push() to eliminate the
shared mutable-state race.

Comment on lines +12 to +26
const fromCol = mockColumns.find(c => c.id === fromColumnId);
const toCol = mockColumns.find(c => c.id === toColumnId);

if (!fromCol || !toCol) {
return json({ error: 'Column not found' }, { status: 404 });
}

const cardIndex = fromCol.cards.findIndex(c => c.id === cardId);
if (cardIndex === -1) {
return json({ error: 'Card not found' }, { status: 404 });
}

// Move the card
const [card] = fromCol.cards.splice(cardIndex, 1);
toCol.cards.push(card);
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

Race condition: shared mutable state without synchronization.

The mockColumns state is shared across all requests and mutated directly without any synchronization mechanism. Concurrent move requests could corrupt the board state (e.g., duplicate cards, lost cards, or cards in wrong columns).

For production use, consider using a proper data store with transactions or request serialization. For demo purposes, this is acceptable but should be documented as not production-ready.

🤖 Prompt for AI Agents
In src/routes/api/kanban/move/+server.ts around lines 12-26, the handler mutates
the shared mockColumns array directly which can lead to race conditions under
concurrent requests; fix by avoiding in-place mutations or adding
synchronization: either (1) treat mockColumns as immutable for request handling
— clone the relevant columns (or the whole board) before removing/pushing the
card and then atomically replace the board reference, or (2) add a simple
mutex/queue around the move logic so only one request mutates mockColumns at a
time, or preferably (3) replace the in-memory mockColumns with a proper
datastore/transactional update for production; also add a comment documenting
that the current in-memory approach is only for demo and not production-safe.

fix: Add input validation for title
docs: Clarify the "optimistic update" comment
docs: handleMove relies on KanbanBoard component mutation
docs: Improve input validation for card title
docs: add warning for API integration example
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: 2

♻️ Duplicate comments (1)
src/routes/api/kanban/cards/+server.ts (1)

27-27: Race condition already flagged in previous review.

This line was already identified in a previous review comment as having a race condition due to mutating shared in-memory state without synchronization. Since this has been acknowledged and is acceptable for demo purposes, no additional action is needed.

</comment_end>

🧹 Nitpick comments (2)
src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1)

33-42: Consider ID collision risk with Date.now().

Using Date.now() for the card ID at line 39 can cause collisions if users rapidly add multiple cards within the same millisecond. While this is acceptable for a demo component, be aware of this limitation.

If you'd like to eliminate this risk, consider using crypto.randomUUID() instead:

-        ? { ...column, cards: [...column.cards, { id: Date.now(), title: title.trim() }] }
+        ? { ...column, cards: [...column.cards, { id: crypto.randomUUID(), title: title.trim() }] }

</comment_end>

src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1)

21-24: Clarify the comment about card movement timing.

The comment on line 22 states "card already moved by KanbanBoard component", but the optimistic UI update happens on line 23 (columns = [...columns]). The actual card move within the columns array is performed by the KanbanBoard component before this handler is called, and line 23 triggers reactivity to reflect that change. Consider revising the comment to clarify this sequence.

Suggested clarification:

  async function handleMove(card: KanbanCardType, from: KanbanColumnType, to: KanbanColumnType) {
-    // Trigger reactivity (card already moved by KanbanBoard component)
+    // Trigger reactivity to reflect the card move performed by KanbanBoard
    columns = [...columns];

</comment_end>

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76b7bf8 and b3e27f1.

📒 Files selected for processing (7)
  • src/routes/api/kanban/cards/+server.ts (1 hunks)
  • src/routes/api/kanban/mockData.ts (1 hunks)
  • src/routes/api/kanban/move/+server.ts (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/LocalStorage.svelte (1 hunks)
  • src/routes/docs/extend/kanban-board.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/routes/api/kanban/mockData.ts
  • src/routes/docs-examples/extend/kanban-board/LocalStorage.svelte
  • src/routes/api/kanban/move/+server.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/api/kanban/cards/+server.ts (2)
src/routes/api/kanban/move/+server.ts (1)
  • POST (6-33)
src/routes/api/kanban/mockData.ts (1)
  • mockColumns (11-35)
🪛 markdownlint-cli2 (0.18.1)
src/routes/docs/extend/kanban-board.md

48-48: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

⏰ 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: Playwright and Lint
🔇 Additional comments (3)
src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1)

29-31: LGTM!

The handleMove function correctly triggers Svelte 5 reactivity by creating a new array reference. This is the proper pattern for signaling state changes.

</comment_end>

src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1)

9-19: LGTM!

The onMount logic properly handles loading state, error scenarios, and successfully fetches the initial board data with appropriate error messaging.

</comment_end>

src/routes/docs/extend/kanban-board.md (1)

1-57: LGTM!

The documentation is well-structured, clear, and provides appropriate warnings about the demo limitations. The sections cover various usage patterns (default, custom styling, localStorage, API integration) with helpful code examples.

</comment_end>

Comment on lines +21 to +25
const newCard = {
id: Date.now(),
title,
tags: ['new']
};
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

Consider collision risk with Date.now() for ID generation.

Using Date.now() as an ID can cause collisions when multiple cards are created in rapid succession (within the same millisecond), especially if multiple concurrent requests complete simultaneously. For a demo this is acceptable, but consider using a more robust approach (e.g., UUID or auto-incrementing counter with proper synchronization) if this pattern is used elsewhere.

</comment_end>

🤖 Prompt for AI Agents
In src/routes/api/kanban/cards/+server.ts around lines 21 to 25, the ID is
generated with Date.now(), which can collide for concurrent creations; replace
this with a robust ID strategy—either use a UUID (e.g., call crypto.randomUUID()
or a UUID/nanoid library) or maintain a synchronized auto-increment counter
persisted in your datastore; update imports if needed and replace id: Date.now()
with the new ID generator so each created card gets a globally-unique
identifier.

docs: Clarify the comment about card movement timing
docs: Fix markdown list indentation
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)
src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1)

33-42: Consider a more user-friendly input method.

The prompt() dialog is functional but provides a dated UX that doesn't integrate with the polished Kanban board styling. For a better user experience, consider using an inline input field or a styled modal dialog that matches the component's visual design.

Note: The logic is sound—immutability is maintained, validation handles edge cases, and new cards receive proper UUID identifiers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3e27f1 and f771cea.

📒 Files selected for processing (3)
  • src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte (1 hunks)
  • src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (1 hunks)
  • src/routes/docs/extend/kanban-board.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/docs-examples/extend/kanban-board/ApiIntegration.svelte
⏰ 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: Playwright and Lint
🔇 Additional comments (4)
src/routes/docs-examples/extend/kanban-board/CustomStyling.svelte (4)

1-2: LGTM!

The imports are clean and follow TypeScript best practices with the explicit type modifier for type-only imports.


4-27: Well-structured state initialization.

The column configuration is clear and properly typed. Initial cards use numeric IDs, which is consistent with typical demo data.


29-31: LGTM!

The shallow copy correctly triggers Svelte 5 reactivity after internal mutations during drag-and-drop operations.


45-55: Excellent demonstration of custom styling capabilities.

The component usage effectively showcases the theming flexibility through the classes prop. The Tailwind utilities provide clear visual enhancements (shadows, transitions, colors) that illustrate how developers can customize the Kanban board's appearance.

Comment on lines +1 to +57
---
layout: componentLayout
title: Svelte Kanban Board
breadcrumb_title: Kanban Board
component_title: Kanban Board
dir: Extend
description: A fully responsive, drag-and-drop Kanban board component built with Svelte 5 and Tailwind CSS v4. Features vertical stacking on mobile, grid layout on larger screens, customizable column colors, card tags, and accessible ARIA labels. Styles are centralized using tailwind-variants for easy theming and maintainability.

---

<script lang="ts">
import { TableProp, TableDefaultRow, CompoAttributesViewer, Seealso, GitHubCompoLinks, LlmLink } from '../../utils'
import { P, A } from '$lib'

const components = 'KanbanBoard'
// const relatedLinks = [''];
</script>

## Setup

```svelte example hideOutput
<script lang="ts">
import { type KanbanCardType, type KanbanColumnType, KanbanBoard } from 'flowbite-svelte';
</script>
```
## Default Kanban Board
Pass columns array with bindable prop, handle onMove and onAddCard callbacks. Supports drag-and-drop between columns with visual feedback.

```svelte example class="flex flex-col space-y-4"
{#include Default.svelte}
```

## Custom Styling

```svelte example class="flex flex-col space-y-4"
{#include CustomStyling.svelte}
```

## Using LocalStorage

```svelte example class="flex flex-col space-y-4"
{#include LocalStorage.svelte}
```

## With API Integration (Demo Only)

**⚠️ Note:** The API endpoints in this example use simple in-memory storage for demonstration purposes. This is **not suitable for production** as it has limitations:
- Data resets on server restart
- Not safe for concurrent requests
- Doesn't scale across multiple server instances

For production, use a proper database (PostgreSQL, MongoDB, Supabase, etc.) with proper transaction handling.


```svelte example class="flex flex-col space-y-4"
{#include ApiIntegration.svelte}
```
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 | 🔴 Critical

🧩 Analysis chain

Markdown structure looks good; previous linting issue resolved.

The documentation is well-structured with proper front matter, clear sections, and good explanatory content. The unordered list items (lines 48-50) now appear to be correctly formatted at column 0, resolving the previous markdown linting issue about indentation.

Please verify that:

  1. All referenced example component files exist in the docs-examples directory (Default.svelte, CustomStyling.svelte, LocalStorage.svelte, ApiIntegration.svelte)
  2. Markdown linting passes with the current formatting (pnpm check)

🏁 Script executed:

#!/bin/bash
# Description: Verify that all referenced example files exist and are accessible

# Find all .svelte example includes referenced in the kanban-board.md file
echo "Checking for referenced example files..."

# Expected examples based on the markdown file
examples=("Default.svelte" "CustomStyling.svelte" "LocalStorage.svelte" "ApiIntegration.svelte")

# Search for these files in the docs-examples directory
for example in "${examples[@]}"; do
  if fd -t f "$example" src/routes/docs-examples/extend/kanban-board/ 2>/dev/null; then
    echo "✓ Found: $example"
  else
    echo "✗ Missing: $example"
  fi
done

echo ""
echo "Checking markdown linting..."

# Check if markdownlint passes (if available)
if command -v markdownlint-cli2 &> /dev/null; then
  markdownlint-cli2 "src/routes/docs/extend/kanban-board.md"
else
  echo "markdownlint-cli2 not found; check will be skipped"
fi

Length of output: 1879


Fix 10 markdown linting errors before merging.

Example files all exist, but markdown linting reveals critical issues that must be resolved:

  • Line 11: Remove inline HTML <script> tag (violates MD033)
  • Lines 12, 27, 47, 52: Reduce line length to 80 characters max (MD013)
  • Line 25: Add blank line before the Setup code fence (MD031)
  • Line 26: Add blank lines around "## Default Kanban Board" heading (MD022)
  • Line 48: Add blank line before the unordered list (MD032)
  • Line 54: Reduce multiple consecutive blank lines to one (MD012)

Run pnpm check to verify all linting passes after fixes.

🤖 Prompt for AI Agents
In src/routes/docs/extend/kanban-board.md lines 1-57: fix the 10 markdown lint
issues by removing the inline HTML <script> tag on line 11 (replace it with a
fenced code block or move script into a proper fenced block), wrap long lines
(lines 12, 27, 47, 52) to <=80 chars by breaking sentences or shortening
imports/docs, add a blank line before the Setup code fence (line 25), add blank
lines above and below the "## Default Kanban Board" heading (line 26) to satisfy
heading spacing, add a blank line before the unordered list (line 48), and
collapse multiple consecutive blank lines to a single blank line (line 54);
after making these edits run pnpm check to verify all markdown lint rules pass.

@shinokada shinokada merged commit d56a5bd into themesberg:main Oct 29, 2025
1 of 2 checks passed
@shinokada shinokada deleted the feat/kanbanboard branch October 29, 2025 09:01
This was referenced Oct 31, 2025
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