From ec1f9721a24b8a104b0e6334bf9683236d0a3c45 Mon Sep 17 00:00:00 2001 From: Sai Prakash Date: Tue, 14 Jul 2026 14:52:44 -0400 Subject: [PATCH 1/2] Add light/dark/system theme switcher, fix low-contrast switch, fix favicon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wire up next-themes (already installed but unused): ThemeProvider in the root layout, a proper light color palette in globals.css (was previously identical to dark), and a Light/Dark/System dropdown in the header. Persists via next-themes' localStorage handling. - Switch component: the unchecked track had a transparent border and relied on a dimmed bg-input fill alone, making it nearly invisible against the near-black page background. Give it a permanent border-border outline and drop the dark-mode opacity reduction. - Favicon: app/icon.tsx (dynamic, on-brand) was fighting with an explicit metadata.icons block pointing at stale "V0" template leftovers in public/ (icon.svg, icon-{light,dark}-32x32.png, apple-icon.png) that didn't match the app's actual logo. Removed the metadata override and the stale files, and added app/apple-icon.tsx so Apple touch icons match too — both are now Next.js file-convention routes generated at build/request time, no .ico needed. Co-Authored-By: Claude Sonnet 5 --- app/apple-icon.tsx | 54 +++++++++++++++++++++++++++ app/globals.css | 72 ++++++++++++++++++------------------ app/layout.tsx | 24 +++--------- components/app-shell.tsx | 3 ++ components/theme-toggle.tsx | 54 +++++++++++++++++++++++++++ components/ui/switch.tsx | 2 +- public/apple-icon.png | Bin 2626 -> 0 bytes public/icon-dark-32x32.png | Bin 585 -> 0 bytes public/icon-light-32x32.png | Bin 566 -> 0 bytes public/icon.svg | 26 ------------- 10 files changed, 153 insertions(+), 82 deletions(-) create mode 100644 app/apple-icon.tsx create mode 100644 components/theme-toggle.tsx delete mode 100644 public/apple-icon.png delete mode 100644 public/icon-dark-32x32.png delete mode 100644 public/icon-light-32x32.png delete mode 100644 public/icon.svg diff --git a/app/apple-icon.tsx b/app/apple-icon.tsx new file mode 100644 index 0000000..f1926a0 --- /dev/null +++ b/app/apple-icon.tsx @@ -0,0 +1,54 @@ +import { ImageResponse } from 'next/og' + +export const size = { + width: 180, + height: 180, +} +export const contentType = 'image/png' + +export default function AppleIcon() { + return new ImageResponse( + ( +
+
+ + + + + +
+
+ ), + { + ...size, + }, + ) +} diff --git a/app/globals.css b/app/globals.css index d9f33b7..6232fcb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,45 +3,45 @@ @custom-variant dark (&:is(.dark *)); -/* Codeplans.ai - Dark theme inspired by Vercel dashboard */ +/* Codeplans.ai - Light theme (mirrors the dark palette below) */ :root { - --background: oklch(0.07 0 0); - --foreground: oklch(0.95 0 0); - --card: oklch(0.11 0 0); - --card-foreground: oklch(0.95 0 0); - --popover: oklch(0.11 0 0); - --popover-foreground: oklch(0.95 0 0); - --primary: oklch(0.95 0 0); - --primary-foreground: oklch(0.07 0 0); - --secondary: oklch(0.18 0 0); - --secondary-foreground: oklch(0.95 0 0); - --muted: oklch(0.15 0 0); - --muted-foreground: oklch(0.6 0 0); - --accent: oklch(0.7 0.15 145); - --accent-foreground: oklch(0.07 0 0); + --background: oklch(0.985 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.6 0.15 145); + --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.55 0.22 25); - --destructive-foreground: oklch(0.95 0 0); - --border: oklch(0.22 0 0); - --input: oklch(0.15 0 0); - --ring: oklch(0.7 0.15 145); - --chart-1: oklch(0.7 0.15 145); - --chart-2: oklch(0.65 0.2 250); - --chart-3: oklch(0.75 0.15 55); - --chart-4: oklch(0.6 0.2 300); - --chart-5: oklch(0.65 0.18 25); + --destructive-foreground: oklch(0.985 0 0); + --border: oklch(0.9 0 0); + --input: oklch(0.9 0 0); + --ring: oklch(0.6 0.15 145); + --chart-1: oklch(0.55 0.15 145); + --chart-2: oklch(0.5 0.2 250); + --chart-3: oklch(0.6 0.15 55); + --chart-4: oklch(0.5 0.2 300); + --chart-5: oklch(0.5 0.18 25); --radius: 0.5rem; - --sidebar: oklch(0.09 0 0); - --sidebar-foreground: oklch(0.95 0 0); - --sidebar-primary: oklch(0.7 0.15 145); - --sidebar-primary-foreground: oklch(0.07 0 0); - --sidebar-accent: oklch(0.15 0 0); - --sidebar-accent-foreground: oklch(0.95 0 0); - --sidebar-border: oklch(0.22 0 0); - --sidebar-ring: oklch(0.7 0.15 145); - --success: oklch(0.7 0.15 145); - --success-foreground: oklch(0.07 0 0); - --warning: oklch(0.75 0.15 55); - --warning-foreground: oklch(0.07 0 0); + --sidebar: oklch(0.97 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.6 0.15 145); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.94 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.9 0 0); + --sidebar-ring: oklch(0.6 0.15 145); + --success: oklch(0.55 0.15 145); + --success-foreground: oklch(0.985 0 0); + --warning: oklch(0.6 0.15 55); + --warning-foreground: oklch(0.985 0 0); } .dark { diff --git a/app/layout.tsx b/app/layout.tsx index 40d8daf..2ccc599 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next' import { Geist, Geist_Mono } from 'next/font/google' import { Analytics } from '@vercel/analytics/next' +import { ThemeProvider } from '@/components/theme-provider' import './globals.css' const _geist = Geist({ subsets: ["latin"] }); @@ -9,23 +10,6 @@ const _geistMono = Geist_Mono({ subsets: ["latin"] }); export const metadata: Metadata = { title: 'CodePlans — Engineering Planning', description: 'Coordinate and track changes across your software architecture. Products, assets, code plans, and tasks in one place.', - icons: { - icon: [ - { - url: '/icon-light-32x32.png', - media: '(prefers-color-scheme: light)', - }, - { - url: '/icon-dark-32x32.png', - media: '(prefers-color-scheme: dark)', - }, - { - url: '/icon.svg', - type: 'image/svg+xml', - }, - ], - apple: '/apple-icon.png', - }, } export default function RootLayout({ @@ -34,9 +18,11 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - + - {children} + + {children} + diff --git a/components/app-shell.tsx b/components/app-shell.tsx index 7aae5f5..6d8906a 100644 --- a/components/app-shell.tsx +++ b/components/app-shell.tsx @@ -15,6 +15,7 @@ import { } from '@/components/ui/dropdown-menu' import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { Badge } from '@/components/ui/badge' +import { ThemeToggle } from '@/components/theme-toggle' import { LayoutDashboard, UserCircle2, @@ -265,6 +266,8 @@ export function AppShell({ children, user, orgName, products, selectedProductId,
+ + diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx new file mode 100644 index 0000000..8f9062f --- /dev/null +++ b/components/theme-toggle.tsx @@ -0,0 +1,54 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useTheme } from 'next-themes' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Sun, Moon, Monitor } from 'lucide-react' +import { cn } from '@/lib/utils' + +const OPTIONS = [ + { value: 'light', label: 'Light', icon: Sun }, + { value: 'dark', label: 'Dark', icon: Moon }, + { value: 'system', label: 'System', icon: Monitor }, +] as const + +export function ThemeToggle() { + const { theme, setTheme } = useTheme() + // Avoid rendering an icon that doesn't match the persisted theme before hydration + const [mounted, setMounted] = useState(false) + useEffect(() => setMounted(true), []) + + const current = OPTIONS.find((o) => o.value === theme) ?? OPTIONS[2] + const CurrentIcon = current.icon + + return ( + + + + + + {OPTIONS.map((option) => { + const Icon = option.icon + return ( + setTheme(option.value)} + className={cn('gap-2', theme === option.value && 'font-medium')} + > + + {option.label} + + ) + })} + + + ) +} diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx index 3c4cfa3..70d5118 100644 --- a/components/ui/switch.tsx +++ b/components/ui/switch.tsx @@ -13,7 +13,7 @@ function Switch({ 0pY4oX7-A^X==I2w?@jsz$PqXc|p*K8~=xx^GNea58PC#Xn&51D#< zxMOkrmHL2MMG`sN(rMKEnP$@Mv;$N%Tk4VhzD+AyUgK|fkNX#03S3-`OJIhqb^9&j z(90`v4D>R670)b1GYxxI>A1KRc)0rhv_r21jp+d@t? zES%GJtMXcbZ+1@3SHY}=3COAs&acDlY!jUqeMygd*%YZ5(Bq^I7973)&6Ri8EEbQF z*k_UTUXws)H~0*`Fz}le9Fzp?_E)jshSAH2pM_TcD(DdC#YX69L7pVwKpk1;Kaimc z_UQ_9jJk0E-& z!6P;PHLkUg{BI+*MW{LIW{IJe7G*qd5@Dnoqj~-1eJ!unIxU~>_1~AoD=V(gq1yCrIo~C&ET5qD%KO2XHDbWi<73QQgE8Dv~hU##+jiPfI zOv<^@*UHDW#)Y!!R|{{h%(>w=G&EFv`a!&F;Bm0_QPw2AD{iAWAhrh@`BNdBf+hB;neb_w zQWzo&^|-p&k;je{jOyKkct$P^kv7LZAkT+QyoYtDN0^Cqo9WpacEX4<(tQcZI;A^6 zmb#Io?PB`Dbt!b~f8tFo&o{T-oK6Ce}DvIRKwI z&w1LJ6golB0_y=F`*pqOie2|DyO+Zx%xPG#POhYT@X0;aZ0^fPgX9NK^YpytMX-?E z%^k65esR3H1bb!HM&r)|o@7DSaMhn)OGyFD5+ip59vjB4cI-!V#(bQu5u2iJR$qE3 zjU1_QEx~3aGp;Z)+Y&^rBaF7FB~Om^m;FSR(>ze^SOmVunxT`mYR5HT8PkESHRFMZvNM7bjY1n6MHy_(p@bX;o?~ z8h{Nj!Tn=;9*nP!UimW5W0txZ2hCA)G3_i*|2)xOWV^h@OeRc6*(|@;GsD zVg=gFGAl6fd$jJLY#JL`q8+K=k1R_dgpNZ6I5yCXCXRuaVHL{)cwi|&?7?6es=e&o z-qJJ#e;{4i*nN2{1K~J3udN7~utx2yQgTk?qspIK>qpzZhuIa(>6f*hl=0?Z9hflD zwY>swB8n88LLE8yacAUF3k&brf(kDVGzLPA4lkOH>Voq@WJle}dmTImj`laS{&z_G zUBM_rs-^X0RTfpM$$PXgd2cF0tmp1M`4r)mFMek?Cil5JA+7~VBSO8|1<8A_v+HjR z0uJ#}Yg_Ar%U*$-)ZIo8Sy9|Anp1@>sbxSn^4+67hkd*H0i~nuKyBeWU=rE^43Z(B3cgGz@<>LJ=#?I3`K7eqgnR@V$Cl1H_hx7At!sCv(b zj@@+q&8UMZuvbKD6XIiMDp;tRy3Si*F95XPiKG#L-+1s>f0TA)+J22<(FjqMn7dr< z_`1%#B`eDrc`o8zKkOleSLf99@>CTJd`9LTcKheoH#nSe^u>Fh3I!Bc%KhTkFYgF5 ziqhQpk}`XDHHquSyAwKM3*dZ(AfSzpMUyh!_@pOFqVA%=SsN2^u|^r>$tiPWPxp|L-+&5 zO^ukm0Zff!As=8VpMc&^&~mGTWgar<%MX)wMM5re-5R{{-t`{IdCE^|M0R2G&E7-{ zcIfOtdWmp-VK^mu@6ht+4p%UwmgUQ7yAwQ_tz+LFy|T>F>H$d8owKCtNg%73#g7?n z9ePrH;Y7fCMS1xf)}`#%F|GM`#L*}nf=Xhm_*Hq`$3K3~+!FTubw`M zq;TB=k1Ne1k;t0RGyE*ktCt%};H5+kyN~AHV5NK^8Dbc~aH)3x-VZzd445Jwr3&0u zmakwGZ+i|L?!n(R{Mve7ExXcNPLJogIM#T%F??qq7`D)b14zHI{;YHK4NLZ#8G0QmB(0vGGX7G3!rX%^s? zeq>ZOl1DU9^)}ae5pk-Ji@gW6T{PMKUBUukxlNACTOIZR>ZLuz^hAyeRM!y2z53Ja zZ;;wCLODVxd*#ZNfl^#iNl7>`adG)_*l@4z+1uZzdy$*Mxp`LEWrn+z2}byy_!bvW zb62?S6%-VNoPPJvtv(8kKavL1Fb@qaK!^mG6K diff --git a/public/icon-dark-32x32.png b/public/icon-dark-32x32.png deleted file mode 100644 index 12c825a109e3c3b7941d957d8a2ac936d5717c5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E5$P)F5K@I)Y9hkG8}6|W~i?1A`6NlJRKhy3zE`f!keoG%R0 zLjeJTG%_U3XczECJ+W9Ul%E@hL8fWaWHO=AXw-HPJ%Obz83z};-L7?CU4%tZlvbfq zEBjzx9^1A}Dr6sIBaY)TBdrV^4u?`@rFb@*wbXi^SI_f8Vm_bOV-;zd3J%)H1n!X; z$~%U-YUfcD(Q37#N6E6R%!<5wkR(aNuWGW6j0n4%|2!eXNTrO$5-O@GSb5HORaRJwL>*7H%p)jeV2su!r z*f@A&t;!dLE_1aH@a~o88UniJApD$%1pw?uB>;9y@TUdjc`mp(TLJ*rORM9l437xC z26nfAN`%~~s)g__*G88?fQq|*4;+uj@<@)yW8#!H=FnjF`+euV)9JL?f=~SaAo&vl z3erab0bit#2t{b|MNE2l9}WhCgfDLYy6X?0V`ueA*$pRw#5ZO?_MS6vLWbO% z$;=!eG9lP9wu((Y*{a{QLvkP)fYfDCmppnQxw1r&haSk4i4VSf(V24@8gKNI90B=3 z4FQro;+;IhyMRx660$6_KCkPV4u=D6w_7@$PFA0%l+-00k4HI~OnUFNNf-n{V+uC4 zx)0{{@n*9j8+0E;V>}+ye!uTWU^E&vDizl2bx&;^$L+ijLgsnij%`s1!%$XLWkiCH z%J6yDcMNrE=Nv9dl7#LBlQ&V(cMq^|!_TM!kWZ)6)_Jj5P!vVEk`@SLyjG)<)q5a61F@a{Ty3(#Ng3V>Zh{;&W(Q@PvidIB)VXKp+Pfz3D^*|nX!x6ubJS7JZlAMP9Jr_FRH=9>9Cn9I^!T - - - - - - - - - - - - - - \ No newline at end of file From eed5f0ed4e2f1ff54c1a06440c10c88efd8d2e15 Mon Sep 17 00:00:00 2001 From: Sai Prakash Date: Tue, 14 Jul 2026 14:53:10 -0400 Subject: [PATCH 2/2] v0.3.20 Co-Authored-By: Claude Sonnet 5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc69c7f..94c7e32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeplans", - "version": "0.3.19", + "version": "0.3.20", "description": "Manage and track coordinated changes across your software architecture.", "author": "Sai Prakash ", "homepage": "https://codeplans.ai",