diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index f19b900..090d5d2 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -765,6 +765,62 @@ body::before {
animation: scaleIn 0.1s ease-out;
}
+/* -----------------------------------------------------------------------------
+ Dashboard Layout β Responsive Grids
+ ----------------------------------------------------------------------------- */
+.metrics-strip {
+ display: grid;
+ grid-template-columns: repeat(var(--ms-cols, 6), 1fr);
+ gap: 0;
+ border-radius: var(--r-lg);
+ overflow: hidden;
+ border: 1px solid var(--border);
+}
+
+.metric-cell {
+ border-left: 1px solid var(--border);
+}
+
+.metric-cell:first-child {
+ border-left: none;
+}
+
+@media (max-width: 900px) {
+ .metrics-strip {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .metric-cell:nth-child(3n + 1) {
+ border-left: none;
+ }
+}
+
+@media (max-width: 640px) {
+ .metrics-strip {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .metric-cell:nth-child(3n + 1) {
+ border-left: 1px solid var(--border);
+ }
+
+ .metric-cell:nth-child(2n + 1) {
+ border-left: none;
+ }
+}
+
+.dash-grid {
+ display: grid;
+ grid-template-columns: 1fr 320px;
+ gap: 16px;
+}
+
+@media (max-width: 900px) {
+ .dash-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
/* -----------------------------------------------------------------------------
Responsive Typography Adjustments
----------------------------------------------------------------------------- */
diff --git a/apps/web/src/components/dashboard/ActivityFeed.tsx b/apps/web/src/components/dashboard/ActivityFeed.tsx
index 97389cb..3a623bf 100644
--- a/apps/web/src/components/dashboard/ActivityFeed.tsx
+++ b/apps/web/src/components/dashboard/ActivityFeed.tsx
@@ -102,8 +102,8 @@ function ActivityItemRow({ item }: ActivityItemRowProps): ReactElement {
style={{
display: "flex",
alignItems: "flex-start",
- gap: 10,
- padding: "8px 0",
+ gap: 12,
+ padding: "10px 0",
borderBottom: "1px solid var(--border)",
}}
>
diff --git a/apps/web/src/components/dashboard/DomainOverviewWidget.tsx b/apps/web/src/components/dashboard/DomainOverviewWidget.tsx
deleted file mode 100644
index 577bf3c..0000000
--- a/apps/web/src/components/dashboard/DomainOverviewWidget.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { Task } from "@mosaic/shared";
-import { TaskStatus, TaskPriority } from "@mosaic/shared";
-
-interface DomainOverviewWidgetProps {
- tasks: Task[];
- isLoading: boolean;
-}
-
-export function DomainOverviewWidget({
- tasks,
- isLoading,
-}: DomainOverviewWidgetProps): React.JSX.Element {
- if (isLoading) {
- return (
-
-
-
-
Loading overview...
-
-
- );
- }
-
- const stats = {
- total: tasks.length,
- inProgress: tasks.filter((t) => t.status === TaskStatus.IN_PROGRESS).length,
- completed: tasks.filter((t) => t.status === TaskStatus.COMPLETED).length,
- highPriority: tasks.filter((t) => t.priority === TaskPriority.HIGH).length,
- };
-
- const StatCard = ({
- label,
- value,
- color,
- }: {
- label: string;
- value: number;
- color: string;
- }): React.JSX.Element => (
-
- );
-
- return (
-
-
Domain Overview
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/web/src/components/dashboard/OrchestratorSessions.tsx b/apps/web/src/components/dashboard/OrchestratorSessions.tsx
index d496470..c9e9732 100644
--- a/apps/web/src/components/dashboard/OrchestratorSessions.tsx
+++ b/apps/web/src/components/dashboard/OrchestratorSessions.tsx
@@ -169,7 +169,7 @@ function OrchCard({ session }: OrchCardProps): ReactElement {
style={{
background: "var(--bg-mid)",
border: "1px solid var(--border)",
- borderRadius: "var(--r-md)",
+ borderRadius: "var(--r)",
padding: "12px 14px",
marginBottom: 10,
}}
diff --git a/apps/web/src/components/dashboard/QuickActions.tsx b/apps/web/src/components/dashboard/QuickActions.tsx
index 0bf4f08..e688e9a 100644
--- a/apps/web/src/components/dashboard/QuickActions.tsx
+++ b/apps/web/src/components/dashboard/QuickActions.tsx
@@ -12,10 +12,10 @@ interface QuickAction {
}
const actions: QuickAction[] = [
- { id: "new-project", label: "New Project", icon: "π", iconBg: "rgba(47,128,255,0.15)" },
- { id: "spawn-agent", label: "Spawn Agent", icon: "π€", iconBg: "rgba(139,92,246,0.15)" },
- { id: "view-telemetry", label: "View Telemetry", icon: "π", iconBg: "rgba(20,184,166,0.15)" },
- { id: "review-tasks", label: "Review Tasks", icon: "π", iconBg: "rgba(245,158,11,0.15)" },
+ { id: "new-project", label: "New Project", icon: "π", iconBg: "rgba(47,128,255,0.12)" },
+ { id: "spawn-agent", label: "Spawn Agent", icon: "π€", iconBg: "rgba(139,92,246,0.12)" },
+ { id: "view-telemetry", label: "View Telemetry", icon: "π", iconBg: "rgba(20,184,166,0.12)" },
+ { id: "review-tasks", label: "Review Tasks", icon: "π", iconBg: "rgba(245,158,11,0.12)" },
];
interface ActionButtonProps {
@@ -36,24 +36,25 @@ function ActionButton({ action }: ActionButtonProps): ReactElement {
}}
style={{
display: "flex",
- flexDirection: "column",
alignItems: "center",
- justifyContent: "center",
gap: 8,
- padding: "16px 12px",
- borderRadius: "var(--r-md)",
+ padding: "10px 12px",
+ borderRadius: "var(--r)",
border: `1px solid ${hovered ? "var(--ms-border-700)" : "var(--border)"}`,
background: hovered ? "var(--surface)" : "var(--bg-mid)",
cursor: "pointer",
- transition: "border-color 0.15s, background 0.15s",
+ transition: "border-color 0.15s, background 0.15s, color 0.15s",
width: "100%",
+ fontSize: "0.8rem",
+ fontWeight: 600,
+ color: hovered ? "var(--text)" : "var(--text-2)",
}}
>
{action.icon}
-
- {action.label}
-
+
{action.label}
);
}
@@ -84,7 +77,7 @@ export function QuickActions(): ReactElement {
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
- gap: 10,
+ gap: 8,
}}
>
{actions.map((action) => (
diff --git a/apps/web/src/components/dashboard/QuickCaptureWidget.tsx b/apps/web/src/components/dashboard/QuickCaptureWidget.tsx
deleted file mode 100644
index 96cac82..0000000
--- a/apps/web/src/components/dashboard/QuickCaptureWidget.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { Button } from "@mosaic/ui";
-import { useRouter } from "next/navigation";
-import { ComingSoon } from "@/components/ui/ComingSoon";
-
-/**
- * Check if we're in development mode (runtime check for testability)
- */
-function isDevelopment(): boolean {
- return process.env.NODE_ENV === "development";
-}
-
-/**
- * Internal Quick Capture Widget implementation
- */
-function QuickCaptureWidgetInternal(): React.JSX.Element {
- const [idea, setIdea] = useState("");
- const router = useRouter();
-
- const handleSubmit = (e: React.SyntheticEvent
): void => {
- e.preventDefault();
- if (!idea.trim()) return;
-
- // TODO: Implement quick capture API call
- // For now, just show a success indicator
- console.log("Quick capture:", idea);
- setIdea("");
- };
-
- const goToTasks = (): void => {
- router.push("/tasks");
- };
-
- return (
-
-
Quick Capture
-
Quickly jot down ideas or brain dumps
-
-
- );
-}
-
-/**
- * Quick Capture Widget (Dashboard version)
- *
- * In production: Shows Coming Soon placeholder
- * In development: Full widget functionality
- */
-export function QuickCaptureWidget(): React.JSX.Element {
- // In production, show Coming Soon placeholder
- if (!isDevelopment()) {
- return (
-
-
-
- );
- }
-
- // In development, show full widget functionality
- return ;
-}
diff --git a/apps/web/src/components/dashboard/RecentTasksWidget.tsx b/apps/web/src/components/dashboard/RecentTasksWidget.tsx
deleted file mode 100644
index 3c864de..0000000
--- a/apps/web/src/components/dashboard/RecentTasksWidget.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { Task } from "@mosaic/shared";
-import { TaskPriority } from "@mosaic/shared";
-import { formatDate } from "@/lib/utils/date-format";
-import { TaskStatus } from "@mosaic/shared";
-import Link from "next/link";
-
-interface RecentTasksWidgetProps {
- tasks: Task[];
- isLoading: boolean;
-}
-
-const statusIcons: Record = {
- [TaskStatus.NOT_STARTED]: "βͺ",
- [TaskStatus.IN_PROGRESS]: "π’",
- [TaskStatus.PAUSED]: "βΈοΈ",
- [TaskStatus.COMPLETED]: "β
",
- [TaskStatus.ARCHIVED]: "π€",
-};
-
-export function RecentTasksWidget({ tasks, isLoading }: RecentTasksWidgetProps): React.JSX.Element {
- if (isLoading) {
- return (
-
- );
- }
-
- const recentTasks = tasks.slice(0, 5);
-
- return (
-
-
-
Recent Tasks
-
- View all β
-
-
- {recentTasks.length === 0 ? (
-
No tasks yet
- ) : (
-
- {recentTasks.map((task) => (
- -
-
- {statusIcons[task.status]}
-
-
-
{task.title}
-
- {task.priority !== TaskPriority.LOW && (
-
- {task.priority}
-
- )}
- {task.dueDate && (
- {formatDate(task.dueDate)}
- )}
-
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/apps/web/src/components/dashboard/UpcomingEventsWidget.tsx b/apps/web/src/components/dashboard/UpcomingEventsWidget.tsx
deleted file mode 100644
index d54a08d..0000000
--- a/apps/web/src/components/dashboard/UpcomingEventsWidget.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { Event } from "@mosaic/shared";
-import { formatTime, formatDate } from "@/lib/utils/date-format";
-import Link from "next/link";
-
-interface UpcomingEventsWidgetProps {
- events: Event[];
- isLoading: boolean;
-}
-
-export function UpcomingEventsWidget({
- events,
- isLoading,
-}: UpcomingEventsWidgetProps): React.JSX.Element {
- if (isLoading) {
- return (
-
- );
- }
-
- const upcomingEvents = events.slice(0, 4);
-
- return (
-
-
-
Upcoming Events
-
- View calendar β
-
-
- {upcomingEvents.length === 0 ? (
-
No upcoming events
- ) : (
-
- {upcomingEvents.map((event) => (
-
-
-
- {formatDate(event.startTime).split(",")[0]}
-
-
- {formatTime(event.startTime)}
-
-
-
-
{event.title}
- {event.location && (
-
π {event.location}
- )}
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/apps/web/src/components/dashboard/__tests__/QuickCaptureWidget.test.tsx b/apps/web/src/components/dashboard/__tests__/QuickCaptureWidget.test.tsx
deleted file mode 100644
index 91cac92..0000000
--- a/apps/web/src/components/dashboard/__tests__/QuickCaptureWidget.test.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * QuickCaptureWidget (Dashboard) Component Tests
- * Tests environment-based behavior
- */
-
-import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
-import { render, screen } from "@testing-library/react";
-import { QuickCaptureWidget } from "../QuickCaptureWidget";
-
-// Mock next/navigation
-vi.mock("next/navigation", () => ({
- useRouter: (): { push: () => void } => ({
- push: vi.fn(),
- }),
-}));
-
-describe("QuickCaptureWidget (Dashboard)", (): void => {
- beforeEach((): void => {
- vi.clearAllMocks();
- });
-
- afterEach((): void => {
- vi.unstubAllEnvs();
- });
-
- describe("Development mode", (): void => {
- beforeEach((): void => {
- vi.stubEnv("NODE_ENV", "development");
- });
-
- it("should render the widget form in development", (): void => {
- render();
-
- // Should show the header
- expect(screen.getByText("Quick Capture")).toBeInTheDocument();
- // Should show the textarea
- expect(screen.getByRole("textbox")).toBeInTheDocument();
- // Should show the Save Note button
- expect(screen.getByRole("button", { name: /save note/i })).toBeInTheDocument();
- // Should show the Create Task button
- expect(screen.getByRole("button", { name: /create task/i })).toBeInTheDocument();
- // Should NOT show Coming Soon badge
- expect(screen.queryByText("Coming Soon")).not.toBeInTheDocument();
- });
-
- it("should have a placeholder for the textarea", (): void => {
- render();
-
- const textarea = screen.getByRole("textbox");
- expect(textarea).toHaveAttribute("placeholder", "What's on your mind?");
- });
- });
-
- describe("Production mode", (): void => {
- beforeEach((): void => {
- vi.stubEnv("NODE_ENV", "production");
- });
-
- it("should show Coming Soon placeholder in production", (): void => {
- render();
-
- // Should show Coming Soon badge
- expect(screen.getByText("Coming Soon")).toBeInTheDocument();
- // Should show feature name
- expect(screen.getByText("Quick Capture")).toBeInTheDocument();
- // Should NOT show the textarea
- expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
- // Should NOT show the buttons
- expect(screen.queryByRole("button", { name: /save note/i })).not.toBeInTheDocument();
- expect(screen.queryByRole("button", { name: /create task/i })).not.toBeInTheDocument();
- });
-
- it("should show description in Coming Soon placeholder", (): void => {
- render();
-
- expect(screen.getByText(/jot down ideas for later organization/i)).toBeInTheDocument();
- });
- });
-
- describe("Test mode (non-development)", (): void => {
- beforeEach((): void => {
- vi.stubEnv("NODE_ENV", "test");
- });
-
- it("should show Coming Soon placeholder in test mode", (): void => {
- render();
-
- // Test mode is not development, so should show Coming Soon
- expect(screen.getByText("Coming Soon")).toBeInTheDocument();
- expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
- });
- });
-});
diff --git a/apps/web/src/components/layout/AppSidebar.tsx b/apps/web/src/components/layout/AppSidebar.tsx
index eca49de..67ea1ec 100644
--- a/apps/web/src/components/layout/AppSidebar.tsx
+++ b/apps/web/src/components/layout/AppSidebar.tsx
@@ -356,7 +356,7 @@ function NavItem({ item, isActive, collapsed }: NavItemProps): React.JSX.Element
alignItems: "center",
gap: "11px",
padding: "9px 10px",
- borderRadius: "6px",
+ borderRadius: "var(--r-sm)",
fontSize: "0.875rem",
fontWeight: 500,
color: isActive ? "var(--text)" : "var(--muted)",
diff --git a/docs/MISSION-MANIFEST.md b/docs/MISSION-MANIFEST.md
index a93be87..724c6b1 100644
--- a/docs/MISSION-MANIFEST.md
+++ b/docs/MISSION-MANIFEST.md
@@ -7,11 +7,11 @@
**ID:** mosaic-stack-go-live-mvp-20260222
**Statement:** Ship Mosaic Stack MVP: operational dashboard with theming, task ingestion, one visible agent cycle, deployed and smoke-tested. Unblocks SagePHR, DYOR, Calibr, and downstream projects.
-**Phase:** Intake
-**Current Milestone:** β
+**Phase:** Execution
+**Current Milestone:** phase-1 (Dashboard Polish + Theming)
**Progress:** 0 / 4 milestones
**Status:** active
-**Last Updated:** 2026-02-22 23:35 UTC
+**Last Updated:** 2026-02-22 23:51 UTC
## Success Criteria
@@ -34,12 +34,12 @@ This mission continues from that foundation.
## Milestones
-| # | ID | Name | Status | Branch | Issue | Started | Completed |
-| --- | ------- | -------------------------- | ------- | ------ | ----- | ------- | --------- |
-| 1 | phase-1 | Dashboard Polish + Theming | pending | β | β | β | β |
-| 2 | phase-2 | Task Ingestion Pipeline | pending | β | β | β | β |
-| 3 | phase-3 | Agent Cycle Visibility | pending | β | β | β | β |
-| 4 | phase-4 | Deploy + Smoke Test | pending | β | β | β | β |
+| # | ID | Name | Status | Branch | Issue | Started | Completed |
+| --- | ------- | -------------------------- | ----------- | ------------------- | ----- | ---------- | --------- |
+| 1 | phase-1 | Dashboard Polish + Theming | in-progress | feat/phase-1-polish | #457 | 2026-02-22 | β |
+| 2 | phase-2 | Task Ingestion Pipeline | pending | β | β | β | β |
+| 3 | phase-3 | Agent Cycle Visibility | pending | β | β | β | β |
+| 4 | phase-4 | Deploy + Smoke Test | pending | β | β | β | β |
## Deployment
@@ -59,8 +59,9 @@ This mission continues from that foundation.
## Session History
-| Session | Runtime | Started | Duration | Ended Reason | Last Task |
-| ------- | ------- | ------- | -------- | ------------ | --------- |
+| Session | Runtime | Started | Duration | Ended Reason | Last Task |
+| ------- | ------- | ---------------- | -------- | ------------ | --------- |
+| S1 | Claude | 2026-02-22 17:50 | β | β | β |
## Scratchpad
diff --git a/docs/TASKS.md b/docs/TASKS.md
index 6748d81..89b064c 100644
--- a/docs/TASKS.md
+++ b/docs/TASKS.md
@@ -2,5 +2,9 @@
> Single-writer: orchestrator only. Workers read but never modify.
-| id | status | milestone | description | pr | notes |
-| --- | ------ | --------- | ----------- | --- | ----- |
+| id | status | milestone | description | pr | notes |
+| --------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------ | --- | -------------------------------------------------------- |
+| MS-P1-001 | done | phase-1 | Fix broken test suites: Button.test.tsx (4 fails, old Tailwind classes) + page.test.tsx (5 fails, old widget refs) | β | issue #457, commit 8fa0b30 |
+| MS-P1-002 | done | phase-1 | Remove legacy unused dashboard widgets: DomainOverviewWidget, RecentTasksWidget, UpcomingEventsWidget, QuickCaptureWidget | β | issue #457, commit 8fa0b30, 5 files deleted |
+| MS-P1-003 | done | phase-1 | Visual + theme polish: audit current vs design reference, fix gaps, verify dark/light across all components, responsive verification | β | issue #457, commit d97a98b, review: approve (0 blockers) |
+| MS-P1-004 | done | phase-1 | Phase verification: all quality gates pass (lint 8/8, typecheck 7/7, test 8/8) | β | issue #457, all gates green (forced, no cache) |
diff --git a/docs/scratchpads/mosaic-stack-go-live-mvp-20260222.md b/docs/scratchpads/mosaic-stack-go-live-mvp-20260222.md
index f05e925..7ce802c 100644
--- a/docs/scratchpads/mosaic-stack-go-live-mvp-20260222.md
+++ b/docs/scratchpads/mosaic-stack-go-live-mvp-20260222.md
@@ -6,15 +6,36 @@
## Original Mission Prompt
```
-(Paste the mission prompt here on first session)
+Continue Mosaic Stack Go-Live MVP from existing state.
+- Mission: Ship Mosaic Stack MVP: operational dashboard with theming, task ingestion,
+ one visible agent cycle, deployed and smoke-tested.
+- 4 milestones: Dashboard Polish+Theming, Task Ingestion Pipeline,
+ Agent Cycle Visibility, Deploy+Smoke Test
+- Prior work: MS15-DashboardShell complete (PRs #451-454)
+- Design ref: mosaic-stack-website/docs/designs/round-5/claude/01/dashboard.html
```
## Planning Decisions
+### 2026-02-22: Phase-1 Task Breakdown
+
+Baseline assessment:
+
+- Lint: PASS, Typecheck: PASS, Tests: FAIL (9 failures)
+- UI Button.test.tsx: 4 fails (old Tailwind class assertions vs new CSS token Button)
+- Web page.test.tsx: 5 fails (old widget layout vs Phase 3 rebuild)
+- Design system + theming already substantially complete from MS15
+- 5 live widgets all use mock data (real data integration is phase-2)
+- 4 legacy widgets unused (hardcoded light theme, pre-Phase 3)
+
+Tasks created: MS-P1-001 through MS-P1-004, issue #457, milestone Go-Live-MVP-Phase1 (0.0.16)
+Estimated total: ~50K tokens
+
## Session Log
-| Session | Date | Milestone | Tasks Done | Outcome |
-| ------- | ---- | --------- | ---------- | ------- |
+| Session | Date | Milestone | Tasks Done | Outcome |
+| ------- | ---------- | --------- | ---------- | ------------------------------------------- |
+| S1 | 2026-02-22 | phase-1 | 0/4 | In progress β bootstrap complete, executing |
## Open Questions
diff --git a/packages/ui/src/components/Button.test.tsx b/packages/ui/src/components/Button.test.tsx
index 2d2d9da..b9da87f 100644
--- a/packages/ui/src/components/Button.test.tsx
+++ b/packages/ui/src/components/Button.test.tsx
@@ -16,19 +16,21 @@ describe("Button", () => {
it("should apply primary variant styles by default", () => {
render();
const button = screen.getByRole("button");
- expect(button.className).toContain("bg-blue-600");
+ expect(button.style.background).toBe("var(--ms-blue-500)");
});
it("should apply secondary variant styles", () => {
render();
const button = screen.getByRole("button");
- expect(button.className).toContain("bg-gray-200");
+ expect(button.style.background).toBe("transparent");
+ expect(button.style.border).toBe("1px solid var(--border)");
});
it("should apply danger variant styles", () => {
render();
const button = screen.getByRole("button");
- expect(button.className).toContain("bg-red-600");
+ expect(button.style.background).toBe("rgba(229, 72, 77, 0.12)");
+ expect(button.style.color).toBe("var(--danger)");
});
});
@@ -81,6 +83,6 @@ describe("Button", () => {
render();
const button = screen.getByRole("button");
expect(button.className).toContain("custom-class");
- expect(button.className).toContain("bg-blue-600");
+ expect(button.style.background).toBe("var(--ms-blue-500)");
});
});
diff --git a/packages/ui/src/components/MetricsStrip.tsx b/packages/ui/src/components/MetricsStrip.tsx
index 5cd84b0..081277c 100644
--- a/packages/ui/src/components/MetricsStrip.tsx
+++ b/packages/ui/src/components/MetricsStrip.tsx
@@ -16,7 +16,7 @@ export interface MetricsStripProps {
className?: string;
}
-function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean }): ReactElement {
+function MetricCellItem({ cell }: { cell: MetricCell }): ReactElement {
const [hovered, setHovered] = useState(false);
const trendColor =
@@ -28,6 +28,7 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
return (
{
setHovered(true);
}}
@@ -37,7 +38,6 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
style={{
padding: "14px 16px",
background: hovered ? "var(--surface-2)" : "var(--surface)",
- borderLeft: isFirst ? "none" : "1px solid var(--border)",
borderTop: `2px solid ${cell.color}`,
transition: "background 0.15s ease",
}}
@@ -82,17 +82,15 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
export function MetricsStrip({ cells, className = "" }: MetricsStripProps): ReactElement {
return (
- {cells.map((cell, index) => (
-
+ {cells.map((cell) => (
+
))}
);