Build SkedPal
KINDAreplaces $9.95/mosaves $119.4/yrback to the verdict
An automatic scheduler whose hard problems are reading a calendar as busy time without learning what is in it, producing a plan that is stable and explainable rather than freshly shuffled every hour, and writing blocks back without ever touching an event it did not create.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the interface.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Builds the desktop shell that holds the task list locally.
Get it rustup.rs, then the Tauri prerequisites for your platform. open ↗
Verify
cargo tauri info reports no missing dependencies - accountfree
Why Writing into a dedicated calendar keeps every mistake visible and reversible, which matters more here than anywhere else in the build.
Get it Create an empty calendar in your account and register an application with the narrowest scope that reads busy time and writes events. open ↗
Verify
The busy query returns intervals and a test event can be created in the new calendar - decidefree
Why Both transitions are asserted in the suite, and deciding after seeing failures means choosing whatever passes.
Get it Write down what a rule landing in the missing hour does and what happens on the repeated one.
Verify
Both are named in the README and in the tests - have readyfree
Why Determinism and stability only look like features against a messy real week; a tidy fixture hides both.
Get it Put your actual flexible work into the tool with honest durations and due dates.
Verify
A plan over the real week reports its shortfall rather than fitting suspiciously well
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- tasks(id, title, total_minutes, remaining_minutes, due_at, earliest_at, priority, min_chunk_minutes, max_chunk_minutes, time_map_id, state) - time_maps(id, name, timezone, rules_json) · when a kind of work is allowed, written in the map's own zone - busy(id, calendar_id, starts_at, ends_at, external_id, is_ours) · start and end and nothing else, because nothing else is fetched - plans(id, computed_at, horizon_days, inputs_sha256, solver_version, unplaceable_json) · the hash makes an identical rerun provable - blocks(id, plan_id, task_id, starts_at, ends_at, placed_by_rule, state, external_event_id, pinned_at) - calendars(id, provider, account_ref, scopes_json, last_synced_at, revoked_at) - The busy table has no title column, so there is nothing for the scheduler, a log or a crash report to leak.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ~/.local/share/scheduler/tasks.db | Tasks, time maps, plans and busy intervals. |
CALENDAR_CLIENT_ID | required | your-application-id | The registered application with the narrow scope. |
CALENDAR_CLIENT_SECRETsecret | required | redacted | Stored in the OS keychain where available, never in the database. |
WRITE_CALENDAR_ID | required | scheduler@group.calendar.google.com | The dedicated calendar. The tool never writes anywhere else. |
PLANNING_HORIZON_DAYS | required | 14 | How far ahead to plan. Longer horizons make the capacity figure more honest and the plan less stable. |
STALE_SYNC_MINUTES | required | 30 | Older than this and availability narrows rather than assuming free time. |
SOLVER_VERSION | required | 1 | Recorded on every plan, so a plan change caused by a code change is distinguishable from one caused by your week. |
The build, in order
Busy time, and not one word more
The calendar is read as intervals with no event content anywhere in the system.
The endpoint that returns less is the endpoint that leaks less.
Files
src/calendar/freebusy.tsThe per-request calendar cap is published and it is not large.
Files
src/calendar/freebusy.tstests/paging.test.tsA schema test is the only version of this promise that stays true.
Files
tests/no-event-content.test.ts
done when · tick each as it passeswatch out- Reading the full event list because it is the easier endpoint puts titles, attendees and locations into your database, your logs and eventually a crash report, and none of that is needed to know that three o'clock is taken. Use the busy query, and assert in the suite that no title, attendee or location column exists anywhere.
Time maps across a clock change
Available slots are computed from rules in a named zone and are right on the two awkward days.
Never by arithmetic on a local midnight.
Files
src/schedule/expand.tstests/dst.test.tsA partial overlap is the case that hand-rolled interval code gets wrong.
Files
src/schedule/slots.tstests/overlap.test.tsTravelling should not silently rewrite when you work.
Files
src/schedule/timemap.ts
done when · tick each as it passeswatch out- Generating slots by adding minutes to a local midnight offers a nine o'clock slot that does not exist on the spring transition and two of them on the autumn one, which is rare enough to pass every test written in June and common enough to wreck a week twice a year. Expand from the zone's rules for that specific date, and put both transition days in the suite.
A plan that is the same plan tomorrow
The solver is deterministic, records why each block landed where it did, and does not reshuffle work already begun.
Including an identifier at the end so nothing is ever a real tie.
Files
src/solver/order.tstests/determinism.test.tsIt turns 'nothing changed' from a feeling into a comparison.
Files
src/solver/plan.tsA scheduler that moves what you already began is a scheduler you stop opening.
Files
src/solver/pins.tstests/stability.test.ts
done when · tick each as it passeswatch out- A solver that replans from scratch every hour moves this afternoon's work to Thursday because a new task arrived, and after the third time nobody believes the calendar; worse, an unstable tie-break makes two runs over identical inputs disagree, so the person cannot tell a real change from noise. Order deterministically, hash the inputs, and hold pinned and started blocks still.
Writing blocks back without stomping
The calendar gains the tool's blocks and loses nothing else.
It makes the blast radius of any mistake visible and small.
Files
src/calendar/write.tsThe range delete is the mistake that removes a real appointment.
Files
src/calendar/write.tstests/never-touch-foreign.test.tsA person moving a block is giving you information, not making an error.
Files
src/calendar/reconcile.ts
done when · tick each as it passeswatch out- Clearing the tool's blocks by deleting every event in a time range removes the dentist appointment that happened to fall inside it, and there is no undo worth the name. Delete only by stored identifier, and if an identifier is missing, leave the event alone and report it.
Telling the truth when it does not fitproduct builder
The tool says plainly what cannot be done and lets you take everything with you.
The shortfall is the most useful output this tool produces.
Files
src/solver/unplaceable.tssrc/views/report.tsxOne number that stops a week being planned on arithmetic that never worked.
Files
src/views/capacity.tsxPlanning against a calendar you can no longer read is worse than not planning.
Files
src/export/run.tssrc/calendar/revoked.ts
done when · tick each as it passeswatch out- Quietly trimming a three hour task into the ninety minutes that happen to be free makes the plan look achievable and the week fail, and because the trim was never reported the person blames themselves. Report the shortfall in hours, refuse to shorten a task without being asked, and show the committed-against-free figure before anyone plans their week around it.
That is the whole plan for SkedPal. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Reading event titles, attendees or locations.
- Cross-device sync.
- Team and shared scheduling.
- Any promise about what you will finish.
- calendar-provider edge cases and timezone correctness
- cross-device sync
- deep operating-system edge-case coverage
- polished shortcuts and accessibility
- vendor-maintained updates
- A second calendar as a read-only busy source, which the busy query already supports
- Recurring tasks that recompute their remaining minutes each period, built on the existing task model
- A weekly review that compares planned blocks against what the calendar says actually happened
Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.