diff --git a/docs/superpowers/plans/2026-04-21-local-cal-redesign.md b/docs/superpowers/plans/2026-04-21-local-cal-redesign.md new file mode 100644 index 0000000..0566f4f --- /dev/null +++ b/docs/superpowers/plans/2026-04-21-local-cal-redesign.md @@ -0,0 +1,663 @@ +# 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"; + +
+
+
+

+ Local Calendar +

+

+ Event timeline +

+
+
+ + {isOnline ? "Online" : "Offline"} + + + + {/* More: Import, Manual create, Settings */} +
+
+ +
+
+ +
+
+
{/* timeline */}
+
+
+
+``` + +- [ ] **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 +

+
+
+ +
+
+