# Local Cal Redesign Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rebuild `local-cal` as a Vercel-inspired product console with aligned desktop AI/timeline hierarchy, capture-first mobile behavior, redesigned primitives, and inline event validation states.
**Architecture:** Start at the token layer in `src/app/globals.css` and the shell contract in `src/lib/ui-shell-contract.ts`, then update shared `ui/*` primitives so pages compose the new visual system instead of layering overrides onto the old glass design. After the primitives are stable, reshape `src/app/page.tsx`, `src/components/ai-toolbar.tsx`, `src/components/event-card.tsx`, `src/components/events-list.tsx`, `src/components/event-dialog.tsx`, and `src/components/settings-panel.tsx` to match the approved desktop/mobile hierarchy and validation behavior.
**Tech Stack:** Next.js 15 App Router, React 19, Tailwind CSS v4, Bun test, Radix UI, Framer Motion
---
## File Structure Map
### Core styling and shell
- Modify: `src/app/globals.css`
- Replace glass-heavy tokens/utilities with the approved neutral product-console token system.
- Modify: `src/lib/ui-shell-contract.ts`
- Re-encode shell surface contracts for the new infrastructure bar, workspace surfaces, and utility controls.
### Shared primitives
- Modify: `src/components/ui/button.tsx`
- Update button variants toward Vercel-like surface, spacing, and focus rules.
- Modify: `src/components/ui/card.tsx`
- Replace default bordered card treatment with shadow-as-border primitives.
- Modify: `src/components/ui/input.tsx`
- Update input treatment to the new console field styling.
- Modify: `src/components/ui/badge.tsx`
- Convert badges toward functional metadata accents instead of generic pills everywhere.
- Modify: `src/components/ui/dialog.tsx`
- Move modal framing from generic bordered surfaces to console-style sheets.
- Modify: `src/components/ui/dropdown-menu.tsx`
- Align menu surfaces with the new token system so `More` and event actions match the redesign.
### Main app surfaces
- Modify: `src/app/page.tsx`
- Rebuild the page hierarchy into a thin infrastructure bar plus aligned top-row AI/timeline workspace.
- Modify: `src/components/ai-toolbar.tsx`
- Make text input and attachments co-equal and adapt desktop/mobile hierarchy.
- Modify: `src/components/events-list.tsx`
- Update empty state and list rhythm to match the new shell.
- Modify: `src/components/event-card.tsx`
- Replace workflow tags with functional metadata emphasis and inline warning treatment.
- Modify: `src/components/event-dialog.tsx`
- Redesign the dialog as a precise console form.
- Modify: `src/components/settings-panel.tsx`
- Demote settings to a secondary admin-like utility surface.
### Validation helpers
- Modify: `src/lib/event-form.ts`
- Expose a reusable event-level validation result that timeline cards can consume.
- Modify: `src/lib/types.ts`
- Add any minimal derived UI type needed for validation summaries if existing types are insufficient.
### Tests
- Modify: `tests/ui-shell-contract.test.ts`
- Lock down the new shell contract classes.
- Modify: `tests/ai-toolbar.test.ts`
- Update AI surface layout contracts around equal text/attachment importance.
- Modify: `tests/event-card.test.ts`
- Add timeline metadata and inline warning assertions.
- Modify: `tests/event-dialog.test.tsx`
- Add dialog contract checks for the redesigned grouping/surface.
- Create: `tests/home-page-layout.test.ts`
- Lock down the approved desktop/mobile hierarchy at the composition level.
---
### Task 1: Replace global tokens and shell contracts
**Files:**
- Modify: `src/app/globals.css`
- Modify: `src/lib/ui-shell-contract.ts`
- Modify: `tests/ui-shell-contract.test.ts`
- [ ] **Step 1: Write the failing shell contract test**
```ts
import { describe, expect, test } from "bun:test";
import {
APP_ACTION_BAR_CLASSES,
APP_HEADER_SURFACE_CLASSES,
APP_NAV_SURFACE_CLASSES,
APP_SECTION_SURFACE_CLASSES,
} from "@/lib/ui-shell-contract";
describe("ui shell contract", () => {
test("header surface is a thin structural bar instead of a glass panel", () => {
expect(APP_HEADER_SURFACE_CLASSES).toContain("min-h-14");
expect(APP_HEADER_SURFACE_CLASSES).toContain("border-b");
expect(APP_HEADER_SURFACE_CLASSES).not.toContain("glass-surface");
});
test("section and action surfaces use tokenized shell classes instead of glass helpers", () => {
expect(APP_SECTION_SURFACE_CLASSES).not.toContain("glass-panel");
expect(APP_ACTION_BAR_CLASSES).not.toContain("glass-subtle");
expect(APP_NAV_SURFACE_CLASSES).not.toContain("glass-surface");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/ui-shell-contract.test.ts`
Expected: FAIL because the shell contract still contains `glass-*` classes and lacks the new structural bar classes.
- [ ] **Step 3: Replace the shell classes with the approved product-console contract**
```ts
import { cn } from "@/lib/utils";
export const APP_HEADER_SURFACE_CLASSES =
"mb-6 flex min-h-14 items-center justify-between gap-3 border-b border-foreground/10 bg-background/95 px-4 py-3 sm:px-6";
export const APP_SECTION_SURFACE_CLASSES =
"rounded-[10px] bg-card px-4 py-4 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa] sm:px-5";
export const APP_ACTION_BAR_CLASSES =
"rounded-[10px] bg-card px-3 py-3 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]";
export const APP_NAV_SURFACE_CLASSES =
"fixed inset-x-4 bottom-4 mx-auto flex max-w-3xl items-center justify-between rounded-[10px] bg-background/95 px-3 py-2 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_8px_24px_rgba(0,0,0,0.08)] sm:inset-x-6 lg:hidden";
const CONNECTION_BADGE_BASE_CLASSES =
"gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium shadow-[0_0_0_1px_rgba(0,0,0,0.08)]";
export const getConnectionBadgeClasses = (isOnline: boolean) =>
cn(
CONNECTION_BADGE_BASE_CLASSES,
isOnline
? "bg-[#ebf5ff] text-[#0068d6]"
: "bg-muted text-muted-foreground",
);
```
- [ ] **Step 4: Replace glass-centric global tokens with the approved light/dark console tokens**
```css
:root {
--background: #ffffff;
--foreground: #171717;
--card: #ffffff;
--card-foreground: #171717;
--popover: #ffffff;
--popover-foreground: #171717;
--primary: #171717;
--primary-foreground: #ffffff;
--secondary: #fafafa;
--secondary-foreground: #171717;
--muted: #fafafa;
--muted-foreground: #666666;
--accent: #f5f5f5;
--accent-foreground: #171717;
--destructive: #ff5b4f;
--destructive-foreground: #ffffff;
--border: #ebebeb;
--input: #ebebeb;
--ring: hsla(212, 100%, 48%, 1);
--radius: 0.625rem;
--shadow-shell: 0 0 0 1px rgba(0, 0, 0, 0.08);
--shadow-card:
0 0 0 1px rgba(0, 0, 0, 0.08),
0 2px 2px rgba(0, 0, 0, 0.04),
0 8px 8px -8px rgba(0, 0, 0, 0.04),
0 0 0 1px #fafafa;
}
.dark {
--background: #111111;
--foreground: #f5f5f5;
--card: #171717;
--card-foreground: #f5f5f5;
--popover: #171717;
--popover-foreground: #f5f5f5;
--primary: #f5f5f5;
--primary-foreground: #171717;
--secondary: #1f1f1f;
--secondary-foreground: #f5f5f5;
--muted: #1a1a1a;
--muted-foreground: #a1a1a1;
--accent: #1f1f1f;
--accent-foreground: #f5f5f5;
--border: rgba(255, 255, 255, 0.1);
--input: rgba(255, 255, 255, 0.12);
}
```
- [ ] **Step 5: Run test to verify it passes**
Run: `bun test tests/ui-shell-contract.test.ts`
Expected: PASS with the new shell contract assertions satisfied.
- [ ] **Step 6: Commit**
```bash
git add src/app/globals.css src/lib/ui-shell-contract.ts tests/ui-shell-contract.test.ts
git commit -m "feat: add product console shell tokens"
```
### Task 2: Redesign shared UI primitives
**Files:**
- Modify: `src/components/ui/button.tsx`
- Modify: `src/components/ui/card.tsx`
- Modify: `src/components/ui/input.tsx`
- Modify: `src/components/ui/badge.tsx`
- Modify: `src/components/ui/dialog.tsx`
- Modify: `src/components/ui/dropdown-menu.tsx`
- Test: `tests/event-dialog.test.tsx`
- [ ] **Step 1: Write the failing dialog and primitive contract test**
```ts
import { describe, expect, test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";
import {
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
describe("dialog primitive redesign", () => {
test("dialog content uses console surface classes instead of generic border+shadow", () => {
const markup = renderToStaticMarkup(
New Event
Console form
,
);
expect(markup).toContain("rounded-[10px]");
expect(markup).toContain("shadow-[0_0_0_1px_rgba(0,0,0,0.08)");
expect(markup).not.toContain("rounded-lg border p-6 shadow-lg");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/event-dialog.test.tsx`
Expected: FAIL because the dialog still renders the old generic bordered modal classes.
- [ ] **Step 3: Update buttons, cards, inputs, badges, and dialog primitives to the new system**
```ts
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[6px] text-sm font-medium transition-[background-color,color,box-shadow] disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/30",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:opacity-92",
outline:
"bg-background text-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.08)] hover:bg-accent",
ghost: "text-muted-foreground hover:bg-accent hover:text-foreground",
link: "text-[#0072f5] underline-offset-4 hover:underline",
},
},
},
);
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
);
}
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
);
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `bun test tests/event-dialog.test.tsx`
Expected: PASS with the redesigned dialog surface contract in the markup.
- [ ] **Step 5: Commit**
```bash
git add src/components/ui/button.tsx src/components/ui/card.tsx src/components/ui/input.tsx src/components/ui/badge.tsx src/components/ui/dialog.tsx src/components/ui/dropdown-menu.tsx tests/event-dialog.test.tsx
git commit -m "feat: redesign shared ui primitives"
```
### Task 3: Rebuild the home page hierarchy
**Files:**
- Modify: `src/app/page.tsx`
- Create: `tests/home-page-layout.test.ts`
- [ ] **Step 1: Write the failing home page hierarchy test**
```ts
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
describe("home page hierarchy", () => {
test("desktop layout defines aligned AI and timeline top-row sections", () => {
const source = readFileSync("src/app/page.tsx", "utf8");
expect(source).toContain("lg:grid-cols-[minmax(0,0.75fr)_minmax(0,1.25fr)]");
expect(source).toContain("AI capture");
expect(source).toContain("Event timeline");
});
test("manual create is routed through a More menu instead of a primary mobile action", () => {
const source = readFileSync("src/app/page.tsx", "utf8");
expect(source).toContain("More");
expect(source).not.toContain("New Event");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/home-page-layout.test.ts`
Expected: FAIL because the page still uses the old stacked shell and direct nav buttons.
- [ ] **Step 3: Reshape `page.tsx` to the approved infrastructure bar plus aligned workspace**
```tsx
const APP_FRAME_CLASSES =
"mx-auto flex min-h-screen w-full max-w-6xl flex-col px-4 pb-20 pt-4 sm:px-6 lg:px-8";
```
- [ ] **Step 4: Run test to verify it passes**
Run: `bun test tests/home-page-layout.test.ts`
Expected: PASS with the aligned desktop workspace and secondary `More` path visible in source.
- [ ] **Step 5: Commit**
```bash
git add src/app/page.tsx tests/home-page-layout.test.ts
git commit -m "feat: rebuild home page hierarchy"
```
### Task 4: Redesign AI capture as a first-class dual-input surface
**Files:**
- Modify: `src/components/ai-toolbar.tsx`
- Modify: `tests/ai-toolbar.test.ts`
- [ ] **Step 1: Write the failing AI capture contract test**
```ts
import { describe, expect, test } from "bun:test";
import { cn } from "@/lib/utils";
const COMPOSER_LAYOUT_CLASSES =
"grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]";
const ATTACHMENTS_PANEL_CLASSES =
"rounded-[10px] bg-card p-3 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]";
describe("AI capture redesign", () => {
test("desktop composer treats prompt and attachments as peer panels", () => {
expect(cn(COMPOSER_LAYOUT_CLASSES)).toContain("lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]");
});
test("attachments panel is a first-class surfaced region, not an inline footer affordance", () => {
expect(cn(ATTACHMENTS_PANEL_CLASSES)).toContain("rounded-[10px]");
expect(cn(ATTACHMENTS_PANEL_CLASSES)).toContain("shadow-[0_0_0_1px_rgba(0,0,0,0.08)]");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/ai-toolbar.test.ts`
Expected: FAIL because the current toolbar still treats attachments as subordinate controls beneath the composer.
- [ ] **Step 3: Refactor the toolbar layout around peer prompt and attachment regions**
```tsx
AI capture
Describe or attach event details
```
- [ ] **Step 4: Run test to verify it passes**
Run: `bun test tests/ai-toolbar.test.ts`
Expected: PASS with the new equal-importance prompt/attachment contract.
- [ ] **Step 5: Commit**
```bash
git add src/components/ai-toolbar.tsx tests/ai-toolbar.test.ts
git commit -m "feat: redesign ai capture surface"
```
### Task 5: Redesign timeline cards and inline validation warnings
**Files:**
- Modify: `src/lib/event-form.ts`
- Modify: `src/components/event-card.tsx`
- Modify: `src/components/events-list.tsx`
- Modify: `tests/event-card.test.ts`
- [ ] **Step 1: Write the failing event card warning test**
```ts
import { describe, expect, test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";
import { EventCard } from "@/components/event-card";
describe("EventCard warning states", () => {
test("renders inline warning copy for invalid event ranges", () => {
const markup = renderToStaticMarkup(
EventCard({
event: {
id: "evt_invalid",
title: "Broken Event",
start: "2026-04-09T11:00:00+00:00",
end: "2026-04-09T10:00:00+00:00",
allDay: false,
},
onEdit: () => {},
onDelete: () => {},
}),
);
expect(markup).toContain("Warning:");
expect(markup).toContain("end time is before start time");
expect(markup).not.toContain("Preview");
expect(markup).not.toContain("Ship");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/event-card.test.ts`
Expected: FAIL because the card has no inline warning support and still assumes the old metadata hierarchy.
- [ ] **Step 3: Add reusable event-level validation and render it in the card**
```ts
export function getEventValidationIssues(event: CalendarEvent): string[] {
const issues: string[] = [];
if (event.end && new Date(event.end).getTime() < new Date(event.start).getTime()) {
issues.push("end time is before start time");
}
if (event.url && !URL.canParse(event.url)) {
issues.push("link is invalid");
}
return issues;
}
```
```tsx
const validationIssues = getEventValidationIssues(event);
{validationIssues.length > 0 && (
Warning: {validationIssues[0]}.
)}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `bun test tests/event-card.test.ts`
Expected: PASS with the inline warning and workflow-tag absence confirmed.
- [ ] **Step 5: Commit**
```bash
git add src/lib/event-form.ts src/components/event-card.tsx src/components/events-list.tsx tests/event-card.test.ts
git commit -m "feat: add inline timeline validation states"
```
### Task 6: Redesign the event dialog and settings surface
**Files:**
- Modify: `src/components/event-dialog.tsx`
- Modify: `src/components/settings-panel.tsx`
- Modify: `tests/event-dialog.test.tsx`
- [ ] **Step 1: Write the failing form-surface test**
```ts
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
describe("event dialog redesign", () => {
test("dialog source uses console section labels and grouped field regions", () => {
const source = readFileSync("src/components/event-dialog.tsx", "utf8");
expect(source).toContain("Event details");
expect(source).toContain("Schedule");
expect(source).toContain("Recurrence");
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bun test tests/event-dialog.test.tsx`
Expected: FAIL because the dialog still renders as one long glass-style form without the new grouped console structure.
- [ ] **Step 3: Group dialog fields into console-style sections and demote settings styling**
```tsx
{titleText}
{descriptionText}
```
```tsx
const settingRowClasses =
"rounded-[10px] bg-card p-4 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]";
```
- [ ] **Step 4: Run test to verify it passes**
Run: `bun test tests/event-dialog.test.tsx`
Expected: PASS with the grouped console sections present in source.
- [ ] **Step 5: Commit**
```bash
git add src/components/event-dialog.tsx src/components/settings-panel.tsx tests/event-dialog.test.tsx
git commit -m "feat: redesign forms and settings surfaces"
```
### Task 7: Final responsive polish and regression verification
**Files:**
- Modify: `src/app/page.tsx`
- Modify: `src/components/ai-toolbar.tsx`
- Modify: `src/components/event-card.tsx`
- Modify: `src/components/settings-panel.tsx`
- Test: `tests/home-page-layout.test.ts`
- Test: `tests/ai-toolbar.test.ts`
- Test: `tests/event-card.test.ts`
- Test: `tests/event-dialog.test.tsx`
- [ ] **Step 1: Add final responsive contract assertions**
```ts
test("mobile layout keeps capture before timeline and keeps manual create secondary", () => {
const source = readFileSync("src/app/page.tsx", "utf8");
expect(source).toContain("order-1 lg:order-none");
expect(source).toContain("Import");
expect(source).toContain("Manual create");
});
```
- [ ] **Step 2: Run the focused redesign suite**
Run: `bun test tests/ui-shell-contract.test.ts tests/home-page-layout.test.ts tests/ai-toolbar.test.ts tests/event-card.test.ts tests/event-dialog.test.tsx`
Expected: PASS for the redesigned shell, AI capture, timeline cards, and dialog contracts.
- [ ] **Step 3: Run the broader regression suite**
Run: `bun test`
Expected: PASS with no regressions in existing event, AI, auth, recurrence, and utility tests.
- [ ] **Step 4: Run build verification**
Run: `bun run build`
Expected: successful Next.js production build with no type or rendering regressions.
- [ ] **Step 5: Commit**
```bash
git add src/app/page.tsx src/components/ai-toolbar.tsx src/components/event-card.tsx src/components/settings-panel.tsx tests/home-page-layout.test.ts tests/ai-toolbar.test.ts tests/event-card.test.ts tests/event-dialog.test.tsx
git commit -m "feat: finalize local cal redesign"
```
## Self-Review
- Spec coverage: the plan covers token replacement, shared primitives, aligned desktop hierarchy, capture-first mobile flow, co-equal attachments, inline validation warnings, dialog redesign, settings demotion, and verification in both light/dark responsive contexts.
- Placeholder scan: no `TODO`, `TBD`, or open-ended implementation placeholders remain in the tasks.
- Type consistency: the same approved concepts are used throughout the plan: aligned AI/timeline top row, timeline-dominant desktop layout, attachment-first AI capture, inline validation warnings, and secondary manual creation.