From e60622f8268b57ce26f74c279865f775ad0b3927 Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:43:23 -0600 Subject: [PATCH 1/7] feat(dashboard): add the toolbar, five status columns, and task cards --- src/app/router.test.tsx | 35 ++++++-- src/components/layout/Sidebar.tsx | 24 +----- src/components/ui/icons.tsx | 22 +++++ src/features/tasks/Dashboard.tsx | 12 ++- src/features/tasks/MyTask.tsx | 12 ++- src/features/tasks/TaskBoard.tsx | 27 +++++++ src/features/tasks/TaskCard.tsx | 56 +++++++++++++ src/features/tasks/Toolbar.tsx | 35 ++++++++ src/features/tasks/icons.tsx | 69 ++++++++++++++++ src/features/tasks/sample-tasks.ts | 124 +++++++++++++++++++++++++++++ 10 files changed, 385 insertions(+), 31 deletions(-) create mode 100644 src/components/ui/icons.tsx create mode 100644 src/features/tasks/TaskBoard.tsx create mode 100644 src/features/tasks/TaskCard.tsx create mode 100644 src/features/tasks/Toolbar.tsx create mode 100644 src/features/tasks/icons.tsx create mode 100644 src/features/tasks/sample-tasks.ts diff --git a/src/app/router.test.tsx b/src/app/router.test.tsx index 5dda587..ea21798 100644 --- a/src/app/router.test.tsx +++ b/src/app/router.test.tsx @@ -22,22 +22,22 @@ function renderAt(path: string) { describe('app routes', () => { it('renders the dashboard at /', () => { renderAt('/') - expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /dashboard/i })).toBeInTheDocument() }) it('renders the settings page at /settings', () => { renderAt('/settings') - expect(screen.getByRole('heading', { name: /settings/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /settings/i })).toBeInTheDocument() }) it('renders the not-found page for unknown paths', () => { renderAt('/does-not-exist') - expect(screen.getByRole('heading', { name: /not found/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /not found/i })).toBeInTheDocument() }) it('renders the my-task placeholder page at /my-task', () => { renderAt('/my-task') - expect(screen.getByRole('heading', { name: /my task/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /my task/i })).toBeInTheDocument() }) }) @@ -56,13 +56,36 @@ describe('sidebar navigation', () => { it('marks nothing active on unknown deep paths (renders not-found)', () => { renderAt('/my-task/anything') - expect(screen.getByRole('heading', { name: /not found/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /not found/i })).toBeInTheDocument() const nav = within(screen.getByRole('navigation')) expect(nav.getByRole('link', { name: /my task/i })).not.toHaveAttribute('aria-current') expect(nav.getByRole('link', { name: /dashboard/i })).not.toHaveAttribute('aria-current') }) }) +describe('dashboard main content', () => { + it('renders the five required status columns', () => { + renderAt('/') + for (const title of ['Backlog', 'To Do', 'In Progress', 'Done', 'Cancelled']) { + expect( + screen.getByRole('heading', { level: 2, name: new RegExp(title, 'i') }), + ).toBeInTheDocument() + } + }) + + it('renders a task card with its required fields', () => { + renderAt('/') + const card = screen.getByRole('heading', { level: 3, name: /twitter/i }).closest('article') + if (!card) throw new Error('expected the Twitter card to render inside an
') + const scoped = within(card) + expect(scoped.getByText(/3 pts/i)).toBeInTheDocument() + expect(scoped.getByText(/yesterday/i)).toBeInTheDocument() + expect(scoped.getByText(/ios app/i)).toBeInTheDocument() + expect(scoped.getByText(/android/i)).toBeInTheDocument() + expect(scoped.getByRole('img', { name: /assignee/i })).toBeInTheDocument() + }) +}) + describe('header', () => { it('renders a controlled search input', async () => { const user = userEvent.setup() @@ -102,7 +125,7 @@ describe('mobile navigation drawer', () => { await user.click(screen.getByRole('button', { name: /open navigation/i })) const nav = within(screen.getByRole('navigation')) await user.click(nav.getByRole('link', { name: /my task/i })) - expect(screen.getByRole('heading', { name: /my task/i })).toBeInTheDocument() + expect(screen.getByRole('heading', { level: 1, name: /my task/i })).toBeInTheDocument() expect(screen.getByRole('button', { name: /open navigation/i })).toHaveAttribute( 'aria-expanded', 'false', diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index da0853a..541af0b 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,28 +1,6 @@ import { NavLink } from 'react-router' import logoUrl from '@/assets/logos/ravn-logomark-white.svg' - -interface IconProps { - className?: string -} - -function DashboardIcon(props: IconProps) { - return ( - - ) -} - -function MyTaskIcon(props: IconProps) { - return ( - - ) -} +import { DashboardIcon, MyTaskIcon } from '@/components/ui/icons' const navItems = [ { to: '/', label: 'Dashboard', Icon: DashboardIcon }, diff --git a/src/components/ui/icons.tsx b/src/components/ui/icons.tsx new file mode 100644 index 0000000..4daa8b0 --- /dev/null +++ b/src/components/ui/icons.tsx @@ -0,0 +1,22 @@ +export interface IconProps { + className?: string +} + +export function DashboardIcon(props: IconProps) { + return ( + + ) +} + +export function MyTaskIcon(props: IconProps) { + return ( + + ) +} diff --git a/src/features/tasks/Dashboard.tsx b/src/features/tasks/Dashboard.tsx index 60078d6..21168eb 100644 --- a/src/features/tasks/Dashboard.tsx +++ b/src/features/tasks/Dashboard.tsx @@ -1,3 +1,13 @@ +import { Toolbar } from '@/features/tasks/Toolbar' +import { TaskBoard } from '@/features/tasks/TaskBoard' +import { sampleColumns } from '@/features/tasks/sample-tasks' + export function Dashboard() { - return

Dashboard

+ return ( +
+

Dashboard

+ + +
+ ) } diff --git a/src/features/tasks/MyTask.tsx b/src/features/tasks/MyTask.tsx index 32c3d04..d8483db 100644 --- a/src/features/tasks/MyTask.tsx +++ b/src/features/tasks/MyTask.tsx @@ -1,3 +1,13 @@ +import { Toolbar } from '@/features/tasks/Toolbar' +import { TaskBoard } from '@/features/tasks/TaskBoard' +import { sampleColumns } from '@/features/tasks/sample-tasks' + export function MyTask() { - return

My Task

+ return ( +
+

My Task

+ + +
+ ) } diff --git a/src/features/tasks/TaskBoard.tsx b/src/features/tasks/TaskBoard.tsx new file mode 100644 index 0000000..052e23e --- /dev/null +++ b/src/features/tasks/TaskBoard.tsx @@ -0,0 +1,27 @@ +import { TaskCard } from '@/features/tasks/TaskCard' +import type { BoardColumn } from '@/features/tasks/sample-tasks' + +export function TaskBoard({ columns }: { columns: BoardColumn[] }) { + return ( +
+ {columns.map(({ title, tasks }) => ( +
+

+ {title} ({String(tasks.length).padStart(2, '0')}) +

+
    + {tasks.map((task) => ( +
  • + +
  • + ))} +
+
+ ))} +
+ ) +} diff --git a/src/features/tasks/TaskCard.tsx b/src/features/tasks/TaskCard.tsx new file mode 100644 index 0000000..af7232a --- /dev/null +++ b/src/features/tasks/TaskCard.tsx @@ -0,0 +1,56 @@ +import avatarUrl from '@/assets/avatar.png' +import { AlarmIcon, AttachIcon, ChatIcon, DotsIcon, ForkIcon } from '@/features/tasks/icons' +import type { SampleTask, TagTone } from '@/features/tasks/sample-tasks' + +const tagToneClasses: Record = { + secondary: 'bg-secondary-4/10 text-secondary-4', + tertiary: 'bg-tertiary-4/10 text-tertiary-4', +} + +export function TaskCard({ task }: { task: SampleTask }) { + return ( +
+
+

+ {task.name} +

+ +
+
+ {task.points} Pts + + + {task.dueLabel} + +
+
+ {task.tags.map(({ label, tone }) => ( + + {label} + + ))} +
+
+ Assignee +
+ + + {task.forks} + + + + {task.comments} + + +
+
+
+ ) +} diff --git a/src/features/tasks/Toolbar.tsx b/src/features/tasks/Toolbar.tsx new file mode 100644 index 0000000..a3ee8b5 --- /dev/null +++ b/src/features/tasks/Toolbar.tsx @@ -0,0 +1,35 @@ +import { DashboardIcon, MyTaskIcon } from '@/components/ui/icons' +import { PlusIcon } from '@/features/tasks/icons' + +export function Toolbar() { + return ( +
+
+
+ Dashboard + +
+
+ Task + +
+
+
+
+ + + + + + +
+ + + +
+ + + +
+ ) +} diff --git a/src/features/tasks/icons.tsx b/src/features/tasks/icons.tsx new file mode 100644 index 0000000..309be83 --- /dev/null +++ b/src/features/tasks/icons.tsx @@ -0,0 +1,69 @@ +import type { IconProps } from '@/components/ui/icons' + +export function PlusIcon(props: IconProps) { + return ( + + ) +} + +export function DotsIcon(props: IconProps) { + return ( + + ) +} + +export function AlarmIcon(props: IconProps) { + return ( + + ) +} + +export function AttachIcon(props: IconProps) { + return ( + + ) +} + +export function ForkIcon(props: IconProps) { + return ( + + ) +} + +export function ChatIcon(props: IconProps) { + return ( + + ) +} diff --git a/src/features/tasks/sample-tasks.ts b/src/features/tasks/sample-tasks.ts new file mode 100644 index 0000000..109b1a6 --- /dev/null +++ b/src/features/tasks/sample-tasks.ts @@ -0,0 +1,124 @@ +export type TagTone = 'secondary' | 'tertiary' + +export interface SampleTask { + id: string + name: string + points: number + dueLabel: string + overdue?: boolean + forks: number + comments: number + tags: { label: string; tone: TagTone }[] +} + +export interface BoardColumn { + title: string + tasks: SampleTask[] +} + +const appTags = [ + { label: 'IOS APP', tone: 'secondary' as const }, + { label: 'ANDROID', tone: 'tertiary' as const }, +] + +export const sampleColumns: BoardColumn[] = [ + { + title: 'Backlog', + tasks: [ + { + id: 'backlog-1', + name: 'Slack', + points: 3, + dueLabel: 'TODAY', + forks: 5, + comments: 3, + tags: appTags, + }, + { + id: 'backlog-2', + name: 'Google', + points: 3, + dueLabel: '6 JULY, 2020', + forks: 5, + comments: 3, + tags: appTags, + }, + ], + }, + { + title: 'To Do', + tasks: [ + { + id: 'todo-1', + name: 'Twitter', + points: 3, + dueLabel: 'YESTERDAY', + overdue: true, + forks: 5, + comments: 3, + tags: appTags, + }, + { + id: 'todo-2', + name: 'Maxxis Tyres', + points: 3, + dueLabel: '6 JULY, 2020', + forks: 5, + comments: 3, + tags: appTags, + }, + ], + }, + { + title: 'In Progress', + tasks: [ + { + id: 'progress-1', + name: 'Samsung', + points: 3, + dueLabel: '6 JULY, 2020', + forks: 5, + comments: 3, + tags: appTags, + }, + { + id: 'progress-2', + name: 'Tesla', + points: 3, + dueLabel: 'YESTERDAY', + overdue: true, + forks: 5, + comments: 3, + tags: appTags, + }, + ], + }, + { + title: 'Done', + tasks: [ + { + id: 'done-1', + name: 'Dashboard Design', + points: 3, + dueLabel: 'TODAY', + forks: 5, + comments: 3, + tags: appTags, + }, + ], + }, + { + title: 'Cancelled', + tasks: [ + { + id: 'cancelled-1', + name: 'Micromax Logo Design', + points: 3, + dueLabel: '6 JULY, 2020', + forks: 5, + comments: 3, + tags: appTags, + }, + ], + }, +] From 50c1bf89110f05c3258f8ac317942d78d29a320d Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:32:56 -0600 Subject: [PATCH 2/7] chore: require a 7-day release age for npm installs --- .npmrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmrc b/.npmrc index b6f27f1..d13caea 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ engine-strict=true +min-release-age=7 From 81062d8c73db1ecf6b6404f4197e0e760b5ca288 Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:34:06 -0600 Subject: [PATCH 3/7] docs: reflect current state of the project --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1069ff3..0184bbb 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ src/ ## What's Implemented - [x] Initial setup (folder structure, routing, styles solution, linting/formatting, error boundary, tests, CI) -- [ ] Dashboard UI (static) +- [x] Dashboard UI (static): sidebar with mobile drawer, header, toolbar, five status columns, task cards - [ ] API connection — fetch tasks, loading/error/empty states - [ ] Create task - [ ] Update task From df6d649bb23d1a6e74a7217aec4020aeaa005454 Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:47:14 -0600 Subject: [PATCH 4/7] refactor(ui): apply review findings, truthful tabs, named icons, domain type --- src/app/router.test.tsx | 17 +++++++++++ src/app/router.tsx | 1 - src/components/layout/Header.tsx | 29 +----------------- src/components/layout/Sidebar.tsx | 6 ++-- src/components/ui/icons.tsx | 28 +++++++++++++++-- src/features/tasks/TaskBoard.tsx | 2 +- src/features/tasks/TaskCard.tsx | 8 +++-- src/features/tasks/Toolbar.tsx | 49 +++++++++++++++++++++++------- src/features/tasks/sample-tasks.ts | 18 +---------- src/features/tasks/types.ts | 17 +++++++++++ 10 files changed, 109 insertions(+), 66 deletions(-) create mode 100644 src/features/tasks/types.ts diff --git a/src/app/router.test.tsx b/src/app/router.test.tsx index ea21798..55f9d8f 100644 --- a/src/app/router.test.tsx +++ b/src/app/router.test.tsx @@ -83,6 +83,23 @@ describe('dashboard main content', () => { expect(scoped.getByText(/ios app/i)).toBeInTheDocument() expect(scoped.getByText(/android/i)).toBeInTheDocument() expect(scoped.getByRole('img', { name: /assignee/i })).toBeInTheDocument() + expect(scoped.getByRole('img', { name: /task options/i })).toBeInTheDocument() + }) + + it('renders the toolbar view icons and the add-task affordance', () => { + renderAt('/') + expect(screen.getByRole('img', { name: /grid view/i })).toBeInTheDocument() + expect(screen.getByRole('img', { name: /list view/i })).toBeInTheDocument() + expect(screen.getAllByRole('img', { name: /add task/i }).length).toBeGreaterThan(0) + }) + + it('marks the correct mobile tab active per route', () => { + renderAt('/my-task') + const taskTab = screen.getByText('Task') + const tabs = taskTab.parentElement?.parentElement + if (!tabs) throw new Error('expected the mobile tabs container to exist') + expect(taskTab).toHaveClass('text-primary-4') + expect(within(tabs).getByText('Dashboard')).toHaveClass('text-neutral-2') }) }) diff --git a/src/app/router.tsx b/src/app/router.tsx index 22e5df6..e21c77c 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -6,7 +6,6 @@ import { Settings } from '@/features/settings/Settings' import { NotFound } from '@/app/NotFound' import { RouteError } from '@/app/RouteError' -// Exported separately so tests can mount the same tree with createMemoryRouter. export const routes: RouteObject[] = [ { path: '/', diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 9ff1b10..16eb692 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,33 +1,6 @@ import { useState } from 'react' import avatarUrl from '@/assets/avatar.png' - -interface IconProps { - className?: string -} - -function SearchIcon(props: IconProps) { - return ( - - ) -} - -function BellIcon(props: IconProps) { - return ( - - ) -} +import { BellIcon, SearchIcon } from '@/components/ui/icons' interface HeaderProps { sidebarOpen: boolean diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 541af0b..871e5cb 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,10 +1,10 @@ import { NavLink } from 'react-router' import logoUrl from '@/assets/logos/ravn-logomark-white.svg' -import { DashboardIcon, MyTaskIcon } from '@/components/ui/icons' +import { GridIcon, ListIcon } from '@/components/ui/icons' const navItems = [ - { to: '/', label: 'Dashboard', Icon: DashboardIcon }, - { to: '/my-task', label: 'My Task', Icon: MyTaskIcon }, + { to: '/', label: 'Dashboard', Icon: GridIcon }, + { to: '/my-task', label: 'My Task', Icon: ListIcon }, ] interface SidebarProps { diff --git a/src/components/ui/icons.tsx b/src/components/ui/icons.tsx index 4daa8b0..dba5173 100644 --- a/src/components/ui/icons.tsx +++ b/src/components/ui/icons.tsx @@ -2,7 +2,7 @@ export interface IconProps { className?: string } -export function DashboardIcon(props: IconProps) { +export function GridIcon(props: IconProps) { return ( ) } + +export function SearchIcon(props: IconProps) { + return ( + + ) +} + +export function BellIcon(props: IconProps) { + return ( + + ) +} diff --git a/src/features/tasks/TaskBoard.tsx b/src/features/tasks/TaskBoard.tsx index 052e23e..7cb96df 100644 --- a/src/features/tasks/TaskBoard.tsx +++ b/src/features/tasks/TaskBoard.tsx @@ -1,5 +1,5 @@ import { TaskCard } from '@/features/tasks/TaskCard' -import type { BoardColumn } from '@/features/tasks/sample-tasks' +import type { BoardColumn } from '@/features/tasks/types' export function TaskBoard({ columns }: { columns: BoardColumn[] }) { return ( diff --git a/src/features/tasks/TaskCard.tsx b/src/features/tasks/TaskCard.tsx index af7232a..e271353 100644 --- a/src/features/tasks/TaskCard.tsx +++ b/src/features/tasks/TaskCard.tsx @@ -1,20 +1,22 @@ import avatarUrl from '@/assets/avatar.png' import { AlarmIcon, AttachIcon, ChatIcon, DotsIcon, ForkIcon } from '@/features/tasks/icons' -import type { SampleTask, TagTone } from '@/features/tasks/sample-tasks' +import type { Task, TagTone } from '@/features/tasks/types' const tagToneClasses: Record = { secondary: 'bg-secondary-4/10 text-secondary-4', tertiary: 'bg-tertiary-4/10 text-tertiary-4', } -export function TaskCard({ task }: { task: SampleTask }) { +export function TaskCard({ task }: { task: Task }) { return (

{task.name}

- + + +
{task.points} Pts diff --git a/src/features/tasks/Toolbar.tsx b/src/features/tasks/Toolbar.tsx index a3ee8b5..22edf26 100644 --- a/src/features/tasks/Toolbar.tsx +++ b/src/features/tasks/Toolbar.tsx @@ -1,33 +1,60 @@ -import { DashboardIcon, MyTaskIcon } from '@/components/ui/icons' +import { useLocation } from 'react-router' +import { GridIcon, ListIcon } from '@/components/ui/icons' import { PlusIcon } from '@/features/tasks/icons' export function Toolbar() { + const { pathname } = useLocation() + const onMyTask = pathname === '/my-task' return (
- Dashboard - + + Dashboard + +
- Task - + + Task + +
- - + + - - + +
- +
- +
diff --git a/src/features/tasks/sample-tasks.ts b/src/features/tasks/sample-tasks.ts index 109b1a6..0b19a20 100644 --- a/src/features/tasks/sample-tasks.ts +++ b/src/features/tasks/sample-tasks.ts @@ -1,20 +1,4 @@ -export type TagTone = 'secondary' | 'tertiary' - -export interface SampleTask { - id: string - name: string - points: number - dueLabel: string - overdue?: boolean - forks: number - comments: number - tags: { label: string; tone: TagTone }[] -} - -export interface BoardColumn { - title: string - tasks: SampleTask[] -} +import type { BoardColumn } from '@/features/tasks/types' const appTags = [ { label: 'IOS APP', tone: 'secondary' as const }, diff --git a/src/features/tasks/types.ts b/src/features/tasks/types.ts new file mode 100644 index 0000000..83b0672 --- /dev/null +++ b/src/features/tasks/types.ts @@ -0,0 +1,17 @@ +export type TagTone = 'secondary' | 'tertiary' + +export interface Task { + id: string + name: string + points: number + dueLabel: string + overdue?: boolean + forks: number + comments: number + tags: { label: string; tone: TagTone }[] +} + +export interface BoardColumn { + title: string + tasks: Task[] +} From 84c24d479ec3ffdc5c4630e105144302e5c27fc0 Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:26:59 -0600 Subject: [PATCH 5/7] chore(security): enforce npm version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 93e7504..0315742 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "version": "0.0.0", "type": "module", "engines": { - "node": ">=24" + "node": ">=24", + "npm": ">=11.10.0" }, "scripts": { "dev": "vite", From bf15ba4112c2f5e9d3ca21c967680ca91691f9a2 Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:55:16 -0600 Subject: [PATCH 6/7] fix(engines): align the Node floor with the npm requirement --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0184bbb..116cb22 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ _Live app and video walkthrough coming soon._ ## Setup & Running Locally -Requires Node 24 (see `.nvmrc`). +Requires Node 24.14.1+ (see `.nvmrc` — its bundled npm 11.11 is the first that enforces `min-release-age`). ```bash git clone https://github.com/Alejandroq12/task-flow.git diff --git a/package.json b/package.json index 0315742..af815cb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "engines": { - "node": ">=24", + "node": ">=24.14.1", "npm": ">=11.10.0" }, "scripts": { From 1a65012402d382c0d798b271ae0213323904b90e Mon Sep 17 00:00:00 2001 From: Julio Quezada <95459740+Alejandroq12@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:00:21 -0600 Subject: [PATCH 7/7] docs(readme): correct the min-release-age version claim --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 116cb22..621889f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ _Live app and video walkthrough coming soon._ ## Setup & Running Locally -Requires Node 24.14.1+ (see `.nvmrc` — its bundled npm 11.11 is the first that enforces `min-release-age`). +Requires Node 24.14.1+ (see `.nvmrc`) — the first Node 24 release whose bundled npm satisfies the `min-release-age` support floor (the feature landed in npm 11.10.0). ```bash git clone https://github.com/Alejandroq12/task-flow.git