-
Notifications
You must be signed in to change notification settings - Fork 322
feat: kanban board #1810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: kanban board #1810
Conversation
|
@shinokada is attempting to deploy a commit to the Themesberg Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds 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
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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) andDate.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-labelattributes 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
📒 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:
KanbanCardandKanbanColumninterfaces properly model the data structureKanbanBoardPropscorrectly extends bothKanbanBoardVariantsandHTMLAttributes<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
handleDropfunction 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
$bindablecolumns prop- Invokes the
onMovecallback with correct arguments- Cleans up drag state
fix: move restProps spread to the container element fix: use CSS custom properties
There was a problem hiding this 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
📒 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
There was a problem hiding this 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
📒 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
KanbanBoardVariantsandKanbanCardVariantsfrom 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
DragEventparameters for accessing event details.
src/lib/kanban/KanbanCard.svelte
Outdated
| ondragstart={() => onDragStart?.(card)} | ||
| ondragend={() => onDragEnd?.()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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
There was a problem hiding this 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 thecolumnsarray for clearer reactivity.The drop logic correctly creates new array references for
fromCol.cardsandtoCol.cards, which should work with Svelte 5's$bindable. However, reassigning the entirecolumnsarray 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
📒 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.
There was a problem hiding this 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
titlefield 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 theKanbanBoardcomponent via thebind:columnsbinding beforehandleMoveis 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
KanbanBoardcomponent to mutate thecolumnsbinding before callingonMove. The reassignmentcolumns = [...columns]triggers reactivity and the$effectsaves 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
📒 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
$effectreactively 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this 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
📒 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>
| const newCard = { | ||
| id: Date.now(), | ||
| title, | ||
| tags: ['new'] | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this 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
📒 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
typemodifier 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
classesprop. The Tailwind utilities provide clear visual enhancements (shadows, transitions, colors) that illustrate how developers can customize the Kanban board's appearance.
| --- | ||
| 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} | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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:
- All referenced example component files exist in the docs-examples directory (
Default.svelte,CustomStyling.svelte,LocalStorage.svelte,ApiIntegration.svelte) - 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"
fiLength 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.
📑 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
✅ Checks
api-checkdirectory as requiredpnpm check && pnpm test:e2eℹ Additional Information
Summary by CodeRabbit
New Features
Tests
Documentation
Style