A Notion-inspired personal productivity dashboard. Tasks, journal, habits,
bookmarks, pages, and a quick note — all in one calm, keyboard-driven
workspace. No backend, no accounts; everything lives in localStorage.
Built with React 18, Vite, Tailwind, TypeScript, and a single D3 chart.
Status: solo project · v0.1 · live at
codejunkie99.github.io/cove
The default view is a dashboard: a hero greeting + clock, sticky section pills, and a 2-column card grid (Today's Focus, Quick Note, Habits, Bookmarks, Journal Streak, Words This Week). Sidebar holds six views; titlebar holds ⌘K and the dark-mode toggle.
Every flow goes through one of two entry points: the sidebar (persistent
nav) or the ⌘K command palette (search + create + jump). Each view has
its own write path; every write hits a small useLocalStorage hook with
cross-tab StorageEvent sync, so two tabs stay in lockstep without a
backend.
- Today dashboard — greeting, time, sticky section pills, six cards.
- Tasks — checkbox toggle on full row, tag chips, inline add, hover-to-delete, progress bar, kanban view with status + priority + emoji + due date.
- Pages — block editor (
paragraph,h1–h3,bullet,numbered,todo,quote,code,callout,divider). - Journal — write today's entry, explicit ⌘S save with unsaved indicator, word count, past-entries list, calendar heatmap streak.
- Habits — 4 habits × 7 days, click cells to toggle, today shown with a dashed orange border.
- Bookmarks — title + URL + colored avatar, quick-add with auto
https://, list and grid views. - Quick note — textarea with
#idea/#work/#personalchips and a live char count. - Command palette (⌘K) — search content across tasks, pages, journal, bookmarks; create tasks/pages; jump views; toggle theme.
- Dark mode — CSS custom properties on
:rootand.dark, smooth transition, persisted. - Responsive — sidebar collapses to an overlay on mobile; padding scales with breakpoints.
- Accessibility —
<button>over<div>, ARIA roles, focus management,sr-onlylabels, keyboard handlers everywhere.
| Layer | Choice |
|---|---|
| Framework | React 18 |
| Bundler | Vite 5 |
| Styling | Tailwind 3 + CSS custom properties |
| Language | TypeScript 5 |
| Charting | D3 (bar chart only) |
| Persistence | window.localStorage |
| Tests | Vitest |
| Hosting | GitHub Pages (gh-pages branch) |
No state library, no router, no backend. State lives in App.tsx and
threads down as props.
cove/
├── docs/
│ ├── ui.svg # Dashboard mockup (this README)
│ └── ux.svg # User-flow / IA diagram (this README)
├── index.html # Vite entry, Inter + DM Mono
├── vite.config.ts # base: '/cove/' for GitHub Pages
├── tailwind.config.js # darkMode: 'class', stone + orange tokens
└── src/
├── main.tsx
├── App.tsx # state hub: types, defaults, useLocalStorage
├── index.css # CSS vars for theming + dark overrides
├── hooks/
│ └── useLocalStorage.ts # cross-tab sync + array validation
├── utils/ # workStats, journalEntries
└── components/
├── Titlebar.tsx
├── Sidebar.tsx
├── MainContent.tsx # view router
├── CommandPalette.tsx
├── BlockEditor.tsx
├── JournalEntryEditor.tsx
├── KanbanBoard.tsx
├── WorkMode.tsx
└── cards/
├── TodayFocusCard.tsx
├── QuickNoteCard.tsx
├── HabitTrackerCard.tsx
├── BookmarksCard.tsx
├── JournalStreakCard.tsx
└── WordsThisWeekCard.tsx
type View = 'today' | 'work' | 'pages' | 'journal'
| 'tasks' | 'bookmarks' | 'archive'
interface Task {
id: string; text: string; done: boolean; tag: string
status?: 'todo' | 'in-progress' | 'in-review' | 'done'
priority?: 'high' | 'medium' | 'low'
emoji?: string; dueDate?: string
}
interface Habit { name: string; color: 'orange' | 'blue'; days: boolean[] }
interface Bookmark { id: string; title: string; url: string; initial: string; color: string; bg: string; createdAt?: number }
interface Page { id: string; title: string; content: string; blocks?: Block[] }
type JournalEntries = Record<string, JournalEntry> // key: YYYY-MM-DD| Key | Shape |
|---|---|
cove-tasks |
Task[] |
cove-pages |
Page[] |
cove-journal |
JournalEntries |
cove-habits |
Habit[] |
cove-bookmarks |
Bookmark[] |
cove-note |
string |
cove-dark |
boolean |
| Token | Value |
|---|---|
| Accent | #fb5607 (orange) — all interactive state |
| Secondary | #3b82f6 (blue) — habit dots + heatmap |
| Background | #ffffff light / #1a1a1a dark |
| Border | #e7e5e4 light / #333333 dark |
| Text — primary | #1c1c1c light / #e5e5e5 dark |
| Sans | Inter, negative tracking on headings |
| Mono | DM Mono, positive tracking on labels/timestamps |
| Radii | 12px cards, 5px checkboxes, 4px chips |
| Card padding | 22px 22px 20px |
| Card shadow | 1px stone border + a barely-there drop shadow |
Voice and tone: warm, direct, quietly personal. "Good morning." not "Hey there! Ready to crush it?"
npm install
npm run dev # http://localhost:5173
npm test # vitest
npm run build # tsc && vite build → dist/
npm run preview # serve dist locally
npm run deploy # gh-pages -d dist (re-publishes to GitHub Pages)| Key | Action |
|---|---|
⌘K / Ctrl K |
Open command palette |
⌘S / Ctrl S |
Save current journal entry |
Esc |
Close palette / overlay sidebar |
↑ ↓ ↩ |
Navigate + commit palette result |
Originally seeded from the NotionOS static template by Terabint, then rebuilt from scratch into a real app. Design system generated with Moonchild and refined by hand.
MIT.