You are browsing as a guest. Sign up (or log in) to start making projects!

Personal Dashboard!

  • 8 Devlogs
  • 18 Total hours

I am making desktop dashboard built with Tauri and a Python backend. It pulls in Canvas assignments, Google Calendar events, a reading list with auto-fetched book covers, project tracking, monthly goals, a Spotify embed, and a personal photo board, all in one draggable, customizable grid that runs locally and saves its layout. How this application improves quality of life: 1. It cuts down on app-switching. Instead of checking Canvas, a calendar app, a reading list, and Spotify separately, everything sits in one place. 2. It tells you when something changes instead of making you check. Canvas gets polled automatically, and new assignments trigger a notification, so nothing depends on remembering to log in. 3. It's actually yours. The layout is draggable, you can drop your own photos in, and the whole thing looks the way you want it to. That matters more than it sounds for whether someone keeps using a tool at all.

Ship #1 Pending review

This started as Canvas won't remind me about anything type project and turned into way more hours than I meant to spend on it lol. I hope it's useful to you too!

The problem
Canvas doesn't proactively tell you about new assignments. You either check it manually and constantly, or you miss things. On top of that, the tools people actually use to manage a semester (calendar, reading list, task list, project tracker) live in five different apps with zero connection between them. This is a self-hosted, single-user desktop app that pulls all of that into one place and checks Canvas automatically so the user doesn't have to.

How to test it

Download the .dmg from the Releases page and open it. Since this is an unsigned build (no Apple Developer account), macOS Gatekeeper will block a normal double-click — right-click the app → Open → confirm. This is documented in the README.
First launch prompts for a name (used in the time-based greeting) and Canvas credentials. There's a collapsible "How do I get this?" section in the onboarding screen itself that walks through generating a Canvas personal access token step by step — no need to leave the app or guess.
Once connected, the dashboard populates with real Canvas assignment data, polling automatically in the background.
From there, everything else is explorable: add a task to the checklist, add a reading list item by pasting a book URL (auto-fetches the cover), add a project, set a monthly goal, add a notable event with a date, start the countdown timer, drop a personal photo into the layout.
The layout itself is customizable — drag widgets to reorder them, drag them between rows, and the grid auto-adjusts to fit. That's configurable further in Settings if someone wants more control over row/column structure.
Google Calendar integration is optional and also has its own in-app walkthrough for generating OAuth credentials, since that's the one part of setup that requires an external Google Cloud step.

Three major QoL improvements

Automatic assignment tracking replaces manual checking. Canvas gets polled on a schedule and the dashboard reflects new assignments without the user having to open Canvas at all.
One dashboard instead of five apps. Assignments, calendar, reading list, tasks, projects, goals, and a timer all live in a single always-open window instead of scattered across separate tools and browser tabs.
A layout that's actually customizable, not fixed. Most dashboard tools hand you a template. This one lets you drag widgets into your own arrangement, resize rows, and drop in your own photos, so the tool ends up looking and working like something built for the person using it, not a generic productivity template.

Scope note

This is intentionally a local, single-user tool; there's no server, no accounts system, no multi-user support. Each person who runs it connects their own Canvas token and their own Google Calendar credentials, entered once during setup and stored securely on their own machine.

Try project → See source code →
Open comments for this post

35m 21s logged

devlog 007: v2 shipped this session.
The launch rendering bug got fixed. Saved checklist items, along with the other affected widgets, were only appearing after new input was added rather than on initial load. The root cause was a gap between the initial data fetch and the render call — the fetch was either running before the backend health check resolved, or the render function was only ever being triggered from the “add” success path and never from the initial dashboard load. Once that was corrected, everything populates immediately on launch the way it should have from the start.
Spotify got the redesign it needed. The old full iframe embed rendered Spotify’s own boxed dark player UI, which clashed with the rest of the dashboard and took up more visual weight than the widget deserved. It’s now a minimal custom player built on Spotify’s oEmbed API — just cover art and a title, no chrome, no background box, sitting flush against the card. Clicking the cover opens the playlist in the browser instead of playing inline, which is a real tradeoff, but it’s the right one for how small this widget is meant to be.
Both fixes went through the full release cycle again: verified in a real running app rather than just read from source, rebuilt with cargo tauri build, and repackaged as a fresh .dmg. The build is tagged and out as v0.2.0.

0
0
5
Open comments for this post

3h 39m 55s logged

devlog 006: This session closed out the zone layout work and pushed the project to a shippable state.
Zone capacity enforcement went in first. Zones now auto-expand their column count when a widget is dragged into a full row rather than rejecting the drop, capped at 4 columns before overflow gets pushed to the next zone. The reverse case also got fixed: when a widget is dragged back out of a zone that had auto-expanded to fit it, the zone shrinks back down and the remaining widgets fill the space instead of leaving a gap. That required tracking which zones were auto-expanded versus manually set in Settings, so a shrink never undoes an intentional user choice.
The palette shifted from dark sepia to a lighter parchment tone this session, then got a second pass after the first attempt left text nearly unreadable against the new light background. Text colors moved to near-black warm browns, and cards got a soft brown drop shadow to give them separation from the background.
A zone count mismatch between the dashboard and settings panel turned out to be two different code paths reading from two different sources of truth instead of one shared value. Fixed by routing both through the same localStorage key.
The bulk of the session was a full release pass. Every feature got audited end to end, name capture and time-based greetings were added to onboarding, collapsible help sections were added explaining how to get a Canvas token and Google OAuth credentials, and the first production build shipped as an unsigned .dmg since there’s no Apple Developer account. The README got rewritten from scratch for a stranger with zero context, including the exact Gatekeeper workaround steps for opening an unsigned app on macOS.
Two issues surfaced immediately after: saved checklist and widget data wasn’t rendering on launch, only appearing after new input was added, which turned out to be an initial-fetch-versus-render-on-add gap rather than a data problem. The Spotify widget also got flagged as visually heavy and is being replaced with a minimal cover-art-and-title player using Spotify’s oEmbed API instead of the full iframe.
v0.2.0 is in progress to address both before the next release.

0
0
3
Open comments for this post

2h 23m 51s logged

devlog 005
This session was almost entirely layout architecture, which went through several pivots before landing somewhere that actually makes sense.
The first attempt was free corner-drag resizing with a Canva-style harmonizer that would proportionally redistribute card widths after every drop or resize. The harmonizer worked in isolation but broke down in practice because SortableJS and flex-wrap fight each other on drop target resolution; the visual drop position and the DOM reorder disagree when cards have arbitrary widths. After two iterations trying to patch that, I scrapped it.
The replacement is a zone-based layout system. The dashboard is divided into horizontal zones, each zone being an independent row with its own column count and height. Zone 1 might have 3 columns, zone 2 might have 2, zone 3 might have 4; each is configured independently in the settings panel. Widgets drag freely within and between zones via SortableJS with cross-group drag enabled. The settings panel has a miniature zone editor with draggable column dividers to set proportional widths, height inputs per zone, and add/remove zone controls. All changes apply live to the dashboard without a save button. Zone definitions persist to localStorage, widget positions to the SQLite layout table.
The immediate open issue is that zones don’t currently enforce their column cap, so widgets can overflow past the zone boundary when dragged in. The fix is straightforward: SortableJS onAdd callback checks current child count against cols, bounces the card back to the source zone if the zone is full, and briefly flashes the target zone border to signal the rejection. Settings col count reductions that would orphan widgets push the overflow to the next zone automatically. That fix is going in next.
Photos are first-class zone items. Height snapping (200/400/500px presets) applies via right-click menu. The snap guide lines and settings inputs from the previous session carried over.

0
0
4
Open comments for this post

1h 35m 38s logged

Devlog 004: This session was mostly infrastructure and debugging rather than new features, but it cleared several blockers that had been compounding across sessions.
The backend crash loop got permanently fixed. The root cause was _exit_if_orphaned in the Python process firing on every Rust hot-reload during npm run tauri dev. When Cargo rebuilds the Tauri binary, the parent PID relationship changes momentarily, which was triggering the orphan detection and killing Python deliberately with exit code 0. The monitor thread interpreted that as a clean exit and did not restart it. The fix was disabling orphan detection entirely in dev mode via the CANVAS_HUB_DEV environment variable, and switching the production check from getppid() == 1 to os.kill(parent_pid, 0) for a more robust existence check. The Rust monitor thread also got crash logging via dirs::home_dir() so it is no longer dependent on the Tauri AppHandle being available from a background thread.
Tile resizing and photo row contagion were also fixed this session, and the actual root causes turned out to be completely different from where previous fixes were looking. The resize buttons were never broken at the event handling level. The real problem was an init race condition: renderPhotos() was fetching /photos before the backend was up, throwing an unhandled error, and killing the rest of initDashboard() before initResizeControls() ever ran. A waitForBackend() health poll now gates all data fetches, so resize listeners are always wired up regardless of how long the backend takes to start. The photo row contagion was caused by height: 100% on the photo resolving to the image’s natural aspect-ratio height during grid track sizing, inflating the row for every neighbor. Switching to position: absolute; inset: 0 on the image fixes the height to the tile bounds and removes it from the track sizing calculation entirely.
Goals this month is now its own separate widget card, extracted from the assignments card where it had been living under a rule separator.

0
0
3
Open comments for this post

2h 34m 34s logged

devlog #004: This session was a chaotic debugging spiral. I set out to finish the draggable grid, but the layout engine absolutely broke.In trying to fix some initial resizing glitches, I overcorrected so hard that I accidentally locked the entire grid down. Nothing could resize at all. Once I untangled that, I hit a new nightmare: whenever I tried to drag a widget, it would interpret the movement as a resize command and stretch wildly across the screen instead of moving.The fixes: 1. Unlocked the layout logic so widgets can actually scale again. 2. Fixed the drag vs. resize conflict. Now, dragging the header moves the widget smoothly, and edge-pulling handles the resizing. No more morphing. It was an exhausting troubleshooting session, but the grid is finally stable.

0
0
3
Open comments for this post

2h 20m 53s logged

devlog 003: personal dashboard
Mostly a fixes session. Nothing new shipped but a lot of things that were broken or annoying got resolved.
The calendar was the biggest one. It had been crammed into a half-width column sharing space with the timer and reading list, which made the weekly view basically unusable. It now takes up the full right two-thirds of the screen, which is what it needed to actually function as a calendar. Time blocks, day columns, navigation arrows, auto-scroll to the current hour on load. It looks right now.
Google Calendar auth also finally works. The onboarding screen had appeared once, I hit “later,” and the flag that tracks whether it had been seen was permanently set with no way to reset it from the UI. A connect button in settings now clears that flag and shows the onboarding immediately, no restart needed. Went through the actual browser consent flow and it works: browser opens, redirect hits the loopback server, token stores correctly.
Two bugs on the smaller side. The photo picker was freezing the app completely because the file dialog was running on the main Tauri thread using the blocking API variant instead of the async one. One-line fix once the cause was clear. The calendar time labels were getting clipped by overflow collapsing the absolute-positioned label column. Widening the column tracks from 36px to 48px cleared it.
Spotify got shrunk. It was taking up a full-width row with nothing in it. Now it is a compact 80px cell in the top strip next to the timer and reading list, which is the right amount of space for it.
Next session: draggable grid with persistent layout.

0
0
3
Open comments for this post

1h 38m 37s logged

devlog 002: personal dashboard
The dashboard is starting to look good!!!
The biggest change was pulling the pink theme entirely and replacing it with a warm sepia palette: dark brown base, aged gold accents, serif headers on everything. I wanted it to feel a little more academic. The layout also got reworked into an asymmetric bento grid, where Canvas takes up a tall left column, the calendar strip runs wide across the bottom right, and the smaller widgets fill in around them.
New widgets added this session: a reading list that resolves book covers from whatever URL you paste in (it checks for an Open Graph image tag first, then falls back to the Google Books API if that fails), an active projects tracker, monthly goals that scope themselves to the current month without deleting older ones, notable events displayed as date pills, a countdown timer that fires a native desktop notification when it hits zero, and photo panels that slot into the bento grid as pure image tiles with no widget chrome around them.
Spotify was simpler than expected. The embedded player works fine once you add Spotify’s domain to the Tauri Content Security Policy config. Without that one line, the iframe loads blank with no error, which took a minute to figure out.
Google Calendar had been stuck because the onboarding screen appeared once, I dismissed it with “later,” and there was no way back to it. Connect and disconnect controls are now in the settings page, so that’s no longer a dead end.
The one remaining item for Google Calendar is entering real credentials from the Google Cloud Console and completing an actual browser OAuth consent flow.

0
0
4
Open comments for this post

3h 10m 5s logged

devlog 001: personal dashboard
got a working desktop app running by end of session. the whole point was to stop missing Canvas assignments; one screen, always open, checks automatically.
backend is FastAPI running as a sidecar inside a Tauri shell, SQLite for storage, APScheduler polling Canvas every 30 minutes. token lives in the OS keychain, never the repo. first-run onboarding handles the whole flow.
the bugs took most of the time. CORS was failing because Tauri dev mode sends a different webview origin than a production build; nothing in the docs tells you this. the /setup endpoint would silently hang because there was no timeout on the Canvas validation request. the dashboard never appeared after login because a display: flex rule was winning over the hidden attribute, so onboarding just stayed on screen even after auth worked fine. and at some point the app started spawning duplicate processes with separate backends, which needed single-instance enforcement to fix.
real credentials work, assignments load from the live API, checklist survives restarts. it’s on GitHub. palette is plum and pink, pulled from my VS Code theme.
next up is Google Calendar.

0
0
2

Followers

Loading…