Targets in, plans out.
DaySet turns a plain list of goals into a structured day — a real timetable with focus blocks, breaks, buffers, a daily priority list, a one-line "quest hook" brief, and a spoken morning greeting. It runs entirely in the browser. Bring your own targets, or paste a sentence in plain English and let the parser turn it into tasks.
The scheduler is a pure function: the same inputs always produce the same plan. That same function powers both the in-app UI and a small, stable SDK that external systems can call directly.
- What it does
- Highlights
- Screenshots / tour
- Tech stack
- Quick start
- Project structure
- The planning engine
- Themes & accents
- Auth & persistence
- DaySet SDK
- Configuration
- Scripts
- Roadmap
- Contributing
- License
Most planners ask you to drag blocks around a calendar. DaySet asks the opposite question: given what I'm trying to do this week, what should today look like?
You give it:
- Targets — name, priority (1–5), estimated duration, preferred time of day, optional deadline, status
- A time scope — start/end date, daily work window, timezone
- A user profile — name, wake time, voice, greeting tone, theme
- Daily context — weather, current datetime, fixed calendar events
And it returns:
- An interactive timetable —
DayPlan[], one per day, each with orderedTimeSlot[](focus / meeting / break / buffer) - In-day priorities — the current "now" block, the next blocks, and a ranked priority list with one-line reasons
- A morning greeting script — text you can have spoken aloud, shaped by your tone preference, name, weather, and the day's plan
- A daily brief per day — a one-line quest hook plus the top targets and total focus time
Done targets drop out of the schedule automatically. Meetings stay where you put them; everything else flows around them.
- Natural-language entry. Paste "I need to write the quarterly report by Friday, finish the API docs, and reply to Sarah's urgent email tonight" — DaySet splits it into targets, infers priority, duration, deadline and time of day.
- Boss tasks & XP. Each day's highest-priority focus block is the boss task. Longer focus blocks pay more XP. It's a thin gamification layer that makes the plan feel like a quest, not a checklist.
- Spoken morning greeting. Wake time, weather and the day's plan become a short script you can play with
speechSynthesis. Pick a male, female or neutral voice and a calm / energetic / playful / serious tone. - Auto-greet. Optionally auto-play the greeting within a configurable window after your wake time, on the weekdays you choose.
- Five themes + custom accent. Modern Dark, Techy (neon grid), Flowers (warm plum), Peaceful (muted teal) — or roll your own accent on any base style. Themes affect background, accent, radius and decor.
- Private by default. Demo mode keeps everything in
localStorage. Sign in with email (Supabase Auth) and the same plan syncs to your account across devices. - Pure, stable SDK. The same
runSdk(input)function the UI calls is exported as a stable contract for external systems. Same inputs, same outputs, every time.
The in-app walkthrough (WalkthroughDialog) steps through the five tabs:
- Now — what's happening this minute, what's next, the priority order
- Schedule — the multi-day timetable with focus blocks, breaks, buffers and fixed meetings
- Briefs — a one-line quest hook and the top targets for any selected day
- Greeting — the morning script with a play button and tone preview
- Settings — target board, time scope, context, profile, theme, account, data, SDK
The landing page (/) shows the marketing pitch, feature grid, the SDK call-to-action with a link to the SDK docs, and the entry CTAs.
| Layer | Choice |
|---|---|
| Framework | React 18 + TypeScript 5.9 |
| Bundler | Vite 7 |
| Styling | Tailwind CSS 4 (@theme tokens, data-theme overrides) |
| Icons | lucide-react |
| Auth & DB | Supabase (auth + Postgres for profiles and targets) |
| Voice | Browser speechSynthesis (no external TTS service) |
| Persistence | localStorage in demo mode, Supabase when signed in |
| Testing | (not yet — see Roadmap) |
No backend of our own. The scheduling engine is pure TypeScript and runs entirely on the client.
# 1. install
npm install
# 2. run the dev server
npm run dev
# 3. build for production
npm run build
npm run previewOpen the URL Vite prints. The first run boots into a splash, then the landing page. Click Try in demo mode to explore with sample data — no account needed.
If you want signed-in sync, the Supabase project credentials are already wired in src/lib/supabase.ts. The expected schema is documented under Auth & persistence.
dayset/
├─ index.html # SEO meta + root div
├─ public/
│ ├─ nativelyai.svg # favicon
│ ├─ robots.txt
│ └─ sitemap.xml
├─ docs/
│ └─ SDK.md # full SDK reference (see DaySet SDK below)
├─ src/
│ ├─ App.tsx # top-level shell: splash → landing → main app
│ ├─ main.tsx # React root
│ ├─ index.css # Tailwind theme tokens + named theme blocks
│ ├─ types.ts # public type contract (Target, DayPlan, SdkInput, …)
│ ├─ data/
│ │ ├─ sample.ts # sample PlanInputs (demo mode)
│ │ └─ themes.ts # theme presets + defaults
│ ├─ engine/
│ │ ├─ scheduler.ts # buildDaySet(inputs) → DaySetOutput (the core)
│ │ ├─ sdk.ts # runSdk(input) + helpers + SDK_EXAMPLE_INPUT
│ │ ├─ natural-language.ts # parseNaturalLanguage(text) → ParsedTodo[]
│ │ ├─ auto-greet.ts # shouldAutoGreet(profile, now) → decision
│ │ ├─ voice.ts # speechSynthesis wrapper
│ │ ├─ time.ts # date / time / duration helpers
│ │ └─ internal-types.ts # re-exports + Assignment helper type
│ ├─ lib/
│ │ ├─ supabase.ts # Supabase client
│ │ └─ db.ts # loadPlan / savePlan (profile + targets rows)
│ └─ components/
│ ├─ Landing.tsx # marketing landing page (links to SDK docs)
│ ├─ SdkDocs.tsx # in-app SDK docs viewer
│ ├─ Splash.tsx # boot splash
│ ├─ AuthScreen.tsx # sign-in / sign-up dialog
│ ├─ OnboardingWizard.tsx # 6-step first-run wizard
│ ├─ Walkthrough.tsx # in-app feature tour with spotlight
│ ├─ NowStrip.tsx # "Now" tab — current + next + priorities
│ ├─ Timetable.tsx # "Schedule" tab — multi-day timetable
│ ├─ Briefs.tsx # "Briefs" tab — daily quest hooks
│ ├─ GreetingCard.tsx # "Greeting" tab — script + play button
│ ├─ SetupPanel.tsx # "Settings" tab — all inputs in one panel
│ └─ ui.tsx # Chip, Dialog, badges, icons
└─ vite.config.ts # React + Tailwind + SVGR plugins
The scheduler lives in src/engine/scheduler.ts and exports one main function:
export function buildDaySet(inputs: PlanInputs): DaySetOutputIt works in four passes per day:
- Free-range computation. Start from the daily work window. Subtract fixed calendar events. What's left is the free time for that day.
- Band allocation. Each target's
preferred_time_of_day(morning / afternoon / evening / flexible) maps to a sub-range of the work window. Flexible targets fill wherever there's room. - Block sizing. A target's
estimated_duration_minutesis split into focus blocks of at most 90 minutes. After each ~90 minutes of focus, a 10-minute break is inserted; short buffers pad transitions between blocks. - Priority & deadline ordering. Targets are ranked by
(priority desc, deadline asc, id). The top-ranked focus block of each day becomes the boss task and earns bonus XP.
XP is sized by block length (small / med / large) and accumulated per day. Energy level (low / med / high) is a gentle hint derived from where the block sits in the day and how much focus came before it.
The in-day priorities view (InDayPriorities) takes the current datetime, finds the slot containing "now", and emits a ranked priority list with one-line reasons computed from deadline distance and status.
The engine is deterministic and side-effect free. The same PlanInputs always produces the same DaySetOutput. This is what makes it safe to expose as an SDK.
Themes are CSS variable sets in src/index.css, switched by data-theme (and data-base for the custom theme) on the root element. Each theme defines:
--color-background,--color-surface,--color-surface-2--color-accent,--color-accent-strong,--color-accent-soft--radius-card,--radius-tile--bg-decor(optional background image — grids, radial blooms)
Accent presets (blue, yellow, mint, coral, custom_hex) are switched by data-accent and override the accent variables. In named themes (Modern Dark, Techy, Flowers, Peaceful) the theme's own accent wins; in Modern Dark the accent preset is applied on top. The custom theme lets you pick any hex on any base style.
Two storage modes:
- Demo mode (default, no sign-in): the plan is persisted to
localStorageunderdayset.plan.v1. UI state (walkthrough seen?) lives underdayset.ui.v1. - Signed-in mode (Supabase Auth, email flow): the plan is loaded from and saved to two Postgres tables, debounced 800 ms after a change.
-- profiles: one row per user, keyed by auth.users.id
create table public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
name text,
wake_time text,
preferred_voice text,
language text,
theme_accent text,
custom_hex text,
theme_name text,
custom_accent_hex text,
custom_base_style text,
greeting_tone text,
auto_greet_enabled boolean,
auto_greet_config jsonb,
onboarding jsonb,
start_date text,
end_date text,
work_window_start text,
work_window_end text,
timezone text,
weather_location text,
weather_summary text,
current_datetime text,
calendar_events jsonb
);
-- targets: one row per target, ordered by position
create table public.targets (
id text primary key,
user_id uuid not null references auth.users(id) on delete cascade,
name text not null,
priority int not null default 3,
estimated_duration_minutes int not null default 45,
preferred_time_of_day text not null default 'flexible',
deadline_date text,
status text not null default 'not_started',
position int not null default 0
);
-- RLS: users can only see and modify their own rows
alter table public.profiles enable row level security;
alter table public.targets enable row level security;
create policy "own profile" on public.profiles
for all using (auth.uid() = id) with check (auth.uid() = id);
create policy "own targets" on public.targets
for all using (auth.uid() = user_id) with check (auth.uid() = user_id);src/lib/db.ts does the load/save and clamps every field back into the type-safe shape — bad rows from an old schema version fall back to safe defaults instead of crashing the app.
The same engine the UI uses is exposed as a small, stable SDK. The full reference lives in docs/SDK.md — the short version:
import { runSdk, SDK_EXAMPLE_INPUT } from "./engine/sdk";
const output = runSdk({
tasks: [
{
id: "t1",
name: "Ship the API",
priority: 5,
estimated_duration_minutes: 120,
preferred_time_of_day: "morning",
deadline_date: "2026-08-04",
status: "not_started",
},
],
user_profile: {
name: "Alex",
wake_time: "06:30",
preferred_voice: "neutral",
language: "en-US",
theme_accent: "blue",
theme_name: "modern_dark",
greeting_tone: "calm",
},
context: {
weather_location: "Kampala",
calendar_events: [],
},
});
// output.schema_version → "1.1"
// output.interactive_timetable → DayPlan[]
// output.in_day_priorities → { now_block, next_blocks, priority_list, focus_recommendation }
// output.morning_greeting_script → string
// output.daily_brief → DailyBrief[]The SDK is pure: no React, no DOM, no network. Run it in a worker, on a server, in another app — anywhere TypeScript runs.
The Supabase URL and publishable key are baked into src/lib/supabase.ts. To point at your own project, edit those two constants. No other configuration is required.
The sample data loaded in demo mode lives in src/data/sample.ts. Edit it to change what a fresh demo visitor sees.
The default theme, default greeting tone and default auto-greet weekdays live in src/data/themes.ts.
| Script | What it does |
|---|---|
npm run dev |
Vite dev server with HMR off (intentional) |
npm run build |
Type-check-agnostic production build |
npm run preview |
Serve the production build locally |
Type-checking is enforced by tsc --noEmit-equivalent rules in tsconfig.json (strict, noUnusedLocals, noUnusedParameters). Run npx tsc --noEmit to check types without building.
- Tests. Vitest + unit tests for
scheduler,natural-language,time,auto-greet, and the SDK round-trip. - CI. GitHub Actions:
lint,typecheck,build,teston every PR. - ESLint + Prettier. No lint config yet.
- Weather integration.
context.weather_summaryis currently free-text. Wire to a real weather API keyed byweather_location. - Calendar sync. Pull fixed events from Google Calendar / iCal instead of typing them in.
- Recurring targets. "Every Monday, review metrics" → auto-create on Monday's plan.
- Mobile build. PWA manifest + service worker for installable offline use.
- Multi-user shared plans. Team rooms with a shared target board.
This repo is being actively pushed to by an automated conductor-sync workflow. To avoid collisions:
- Work on a feature branch (
feat/<name>orfix/<name>), notmain. - Open a PR. CI will run when it exists.
- Keep the engine pure. If you add a side effect to
scheduler.tsorsdk.ts, you've broken the SDK contract — don't.
For style: 2-space indent, semi, double quotes for strings, single-line import braces where short. Match the surrounding file.
All rights reserved for now. A permissive license will likely be added before any public release.