What I Learned Building a Canvas Design and Animation Editor
Notes from building Kinetica, a browser design and animation editor: one canvas renderer for editor, previews and exports, timeline keyframes and undo.
StudyPilot is a study manager I built that runs entirely in the browser. It has subjects, tasks, flashcards, a focus timer, analytics and a dashboard you can rearrange. It has no backend, no accounts and no AI API. The part I was most curious about was the planner, which answers one question: what should I study today? It does this with plain TypeScript rules, not a model.
I wanted StudyPilot to open instantly, work without a connection and not ask anyone to sign up. Without a server there is nothing to host but static files.
All app state lives in one Zustand store wrapped in Zustand's persist middleware, which saves to localStorage under the key studypilot:v1. A partialize function picks what gets saved: subjects, tasks, cards, sessions, notes, settings, dashboard widgets and the timer.
Two details mattered:
skipHydration: true, and the app shell calls useStore.persist.rehydrate() after it mounts. Next.js renders the page before browser storage is available, so the saved data is loaded only once the app is running in the browser.studypilot-backup-YYYY-MM-DD.json file and import it again.On first launch the store is filled with demo data, which you can clear with one click.
The dashboard is made of widgets: today's plan, tasks, subjects, focus timer, study hours, deadlines and exams, streak, quick notes, flashcards due and weekly goal. You can drag them into a new order (with dnd-kit), add or remove them, and resize each one from 1 to 4 columns wide and 1 to 3 rows tall. Each widget is stored as a small config object with an id, type, width and height, in the same persisted store as everything else. So the layout is saved like your tasks and is included in backups.
All the planner logic is in src/lib/planner.ts. The functions are pure: same data in, same plan out. This made them easy to test with Vitest.
Every open task gets a score. Higher means "do this sooner":
export function taskScore(task: Task, subjects: Subject[], today: string): number {
let s = PRIORITY_WEIGHT[task.priority] * 10;
if (task.due) {
const d = diffDays(today, task.due);
if (d < 0) s += 50 + Math.min(-d, 7) * 3;
else s += Math.max(0, 30 - d * 4);
} else s -= 5;
const subj = subjects.find((x) => x.id === task.subjectId);
if (subj?.examDate) {
const e = diffDays(today, subj.examDate);
if (e >= 0 && e <= 14) s += (14 - e) * 1.5;
}
if (subj) s += (subj.difficulty - 1) * 2;
if (task.status === "doing") s += 8;
if (task.scheduledFor === today) s += 12;
else if (task.scheduledFor && task.scheduledFor > today) s -= 15;
return Math.round(s * 10) / 10;
}
Priority sets the base. Overdue work gets a large boost, and nearby deadlines get a smaller one. An exam within 14 days, a harder subject or a task already in progress pushes the score up. A task scheduled for a later day is pushed down. A matching taskReason function turns the same signals into a label like "Due tomorrow · high priority".
buildDailyPlan turns those scores into a timed schedule. Your capacity is your daily study hours minus what you've already logged today. Then it fills time in this order:
After any block of 25 minutes or more, a break is added (if breaks are turned on). The plan never runs past midnight, so if you start late you get a shorter plan, not an impossible one.
autoSchedule plans the next two weeks, earliest deadline first. Each task goes on the first day before its deadline that still has room. If no day has room, it goes on the least-loaded day and is flagged "at risk". Tasks without a deadline are spread over the least-busy days of the coming week. The assistant panel uses these results for its suggestions, such as rescheduling overdue work or warning that a deadline is at risk, and each suggestion comes with a one-click action.
Not every "smart" feature needs a model. A clear scoring function and a few ordered rules did the job here, and since every rule is in one file, anyone can read why the planner suggested something. Keeping the logic pure also made the tests simple to write. One checks that it "never plans past midnight".
You can try StudyPilot at study-manager-fawn.vercel.app, and the source is on GitHub at mfaysal-dev/study-manager.
// note
This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.
If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.
Notes from building Kinetica, a browser design and animation editor: one canvas renderer for editor, previews and exports, timeline keyframes and undo.
How I built Quizwright so the Gemini API key never reaches the browser: one Next.js server route, a server-only import, Zod checks and a single retry.
A sample article for the blog layout. It is a practice note, not a finished tutorial or a claim of expertise.