A local-first note taking app
0

Configure Feed

Select the types of activity you want to include in your feed.

Initialize style system

Ethan Graf (May 5, 2026, 8:25 PM EDT) a21e009d 896bcbeb

+85 -30
+1
.prettierignore
··· 5 5 coverage 6 6 *.log 7 7 package-lock.json 8 + PRODUCT.md
+2 -1
.prettierrc.json
··· 1 1 { 2 - "singleQuote": true 2 + "singleQuote": true, 3 + "plugins": ["prettier-plugin-tailwindcss"] 3 4 }
+3
package.json
··· 27 27 "@electron-forge/plugin-fuses": "^7.11.1", 28 28 "@electron-forge/plugin-vite": "^7.11.1", 29 29 "@electron/fuses": "^1.8.0", 30 + "@tailwindcss/vite": "^4.2.4", 30 31 "@types/electron-squirrel-startup": "^1.0.2", 31 32 "@types/react": "^19.2.14", 32 33 "@types/react-dom": "^19.2.3", ··· 39 40 "eslint-import-resolver-typescript": "^4.4.4", 40 41 "eslint-plugin-import": "^2.32.0", 41 42 "prettier": "^3.8.3", 43 + "prettier-plugin-tailwindcss": "^0.8.0", 44 + "tailwindcss": "^4.2.4", 42 45 "typescript": "~6.0.3", 43 46 "vite": "^8.0.10" 44 47 },
+3 -2
vite.renderer.config.ts
··· 1 - import { defineConfig } from 'vite'; 1 + import tailwindcss from '@tailwindcss/vite'; 2 2 import react from '@vitejs/plugin-react'; 3 + import { defineConfig } from 'vite'; 3 4 4 5 // https://vitejs.dev/config 5 6 export default defineConfig({ 6 - plugins: [react()], 7 + plugins: [tailwindcss(), react()], 7 8 });
+36 -4
src/App.tsx
··· 1 + import { useEffect, useState } from 'react'; 1 2 import { MemoryRouter, NavLink, Route, Routes } from 'react-router-dom'; 2 3 import { AboutPage } from './pages/AboutPage'; 3 4 import { HomePage } from './pages/HomePage'; 4 5 6 + const navLinkClass = ({ isActive }: { isActive: boolean }) => 7 + [ 8 + 'rounded px-2 py-1 text-foreground transition-colors', 9 + isActive 10 + ? 'bg-accent/15 font-semibold text-accent underline decoration-accent underline-offset-2' 11 + : 'text-foreground/80 hover:bg-border/40 hover:text-foreground', 12 + ].join(' '); 13 + 5 14 export default function App() { 15 + const [theme, setTheme] = useState<'light' | 'dark'>(() => { 16 + if (typeof document === 'undefined') return 'light'; 17 + return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; 18 + }); 19 + 20 + useEffect(() => { 21 + const root = document.documentElement; 22 + if (theme === 'dark') { 23 + root.dataset.theme = 'dark'; 24 + } else { 25 + root.removeAttribute('data-theme'); 26 + } 27 + }, [theme]); 28 + 6 29 return ( 7 30 <MemoryRouter initialEntries={['/']}> 8 - <header> 9 - <nav> 10 - <NavLink to="/" end> 31 + <header className="border-border mb-6 flex flex-wrap items-center justify-between gap-4 border-b pb-4"> 32 + <nav className="flex flex-wrap gap-2"> 33 + <NavLink to="/" end className={navLinkClass}> 11 34 Home 12 35 </NavLink> 13 - <NavLink to="/about">About</NavLink> 36 + <NavLink to="/about" className={navLinkClass}> 37 + About 38 + </NavLink> 14 39 </nav> 40 + <button 41 + type="button" 42 + className="border-border bg-background text-foreground hover:bg-border/30 rounded border px-3 py-1.5 text-sm" 43 + onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))} 44 + > 45 + Theme: {theme} 46 + </button> 15 47 </header> 16 48 <main> 17 49 <Routes>
+32 -17
src/index.css
··· 1 - body { 2 - font-family: 3 - -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, 4 - sans-serif; 5 - margin: auto; 6 - max-width: 38rem; 7 - padding: 2rem; 1 + @import 'tailwindcss'; 2 + 3 + /* Semantic tokens (swap per user theme by changing these variables). */ 4 + :root { 5 + --semantic-bg: oklch(0.99 0 0); 6 + --semantic-fg: oklch(0.22 0 0); 7 + --semantic-accent: oklch(0.52 0.17 260); 8 + --semantic-border: oklch(0.9 0 0); 8 9 } 9 10 10 - nav { 11 - display: flex; 12 - gap: 1rem; 13 - margin-bottom: 1.5rem; 11 + :root[data-theme='dark'] { 12 + --semantic-bg: oklch(0.18 0.01 260); 13 + --semantic-fg: oklch(0.96 0 0); 14 + --semantic-accent: oklch(0.72 0.14 260); 15 + --semantic-border: oklch(0.32 0.02 260); 14 16 } 15 17 16 - nav a { 17 - color: inherit; 18 - text-decoration: none; 18 + @theme { 19 + --color-background: var(--semantic-bg); 20 + --color-foreground: var(--semantic-fg); 21 + --color-accent: var(--semantic-accent); 22 + --color-border: var(--semantic-border); 19 23 } 20 24 21 - nav a.active { 22 - font-weight: 600; 23 - text-decoration: underline; 25 + @layer base { 26 + body { 27 + @apply bg-background text-foreground mx-auto max-w-xl p-8 antialiased; 28 + font-family: 29 + ui-sans-serif, 30 + system-ui, 31 + -apple-system, 32 + BlinkMacSystemFont, 33 + 'Segoe UI', 34 + Roboto, 35 + Helvetica, 36 + Arial, 37 + sans-serif; 38 + } 24 39 }
+3 -3
src/pages/AboutPage.tsx
··· 1 1 export function AboutPage() { 2 2 return ( 3 - <section> 4 - <h1>About</h1> 5 - <p> 3 + <section className="space-y-2"> 4 + <h1 className="text-foreground text-2xl font-semibold">About</h1> 5 + <p className="text-foreground/90"> 6 6 Routes use MemoryRouter so navigation does not depend on a server URL. 7 7 </p> 8 8 </section>
+5 -3
src/pages/HomePage.tsx
··· 1 1 export function HomePage() { 2 2 return ( 3 - <section> 4 - <h1>Home</h1> 5 - <p>Renderer is running React inside Electron.</p> 3 + <section className="space-y-2"> 4 + <h1 className="text-foreground text-2xl font-semibold">Home</h1> 5 + <p className="text-foreground/90"> 6 + Renderer is running React inside Electron. 7 + </p> 6 8 </section> 7 9 ); 8 10 }