2025 · live

Star-Office

8-bit pixel office that visualises your real work — GitHub commits, meetings, unfinished agent tasks — as a sprite-filled room.

TypeScript · Svelte 5 · Vite · Canvas API · GitHub API · iCal · Zustand · Vercel

GitHub stars
624

current

Weekly active users
~420

last 4 weeks

Production self-host deploys
3

teams I know of

Hacker News rank
#7

peak on launch day

The problem

Dashboards are broken. They're functional, sure, but they're also sterile, joyless grids of numbers and charts. Tools like Grafana, Linear, and even GitHub's own Insights page are great at telling you what happened, but they completely fail to capture the feeling of a productive week. They turn creative work into cold, hard metrics. My calendar isn't a list of time slots; it's a series of conversations. My commit history isn't a bar chart; it's a trail of problems solved and features built.

I wanted a dashboard that felt less like a spreadsheet and more like a space. I'm a huge fan of games like Stardew Valley and Animal Crossing, where your environment reflects your progress and activity. What if my work dashboard looked like a cozy room in one of those games? What if, instead of a number, a new commit placed a book on a shelf? What if an upcoming meeting was a person waiting in a chair? I wanted to build a visualization of my work that felt human, tangible, and a little bit magical.

The constraint

From the outset, this was a weekend project, which imposed some strict, but ultimately helpful, constraints.

First, it had to run entirely in a browser tab with no backend for the default public deployment. This meant all data fetching, parsing, and state management had to happen client-side. It simplified deployment immensely (just static files on Vercel) and addressed privacy concerns, as user data would never touch my servers.

Second, it had to use real, live data. This wasn't a toy that showed fake activity. It needed to connect to the GitHub API for commit history and collaborator data, and parse iCal feeds for calendar events. The value was in seeing your actual work reflected in the room.

Third, the performance budget was tight. For a dashboard you might leave open all day, it had to be lightweight. I set a target of under 300KB of shipped JavaScript. This forced me to be judicious with dependencies and choose my framework carefully.

How the room is drawn

The entire scene is rendered on a single HTML5 <canvas> element. The internal resolution is a tiny 256×192 pixels, true to the 8-bit aesthetic. This low-resolution canvas is then scaled up to fill a 1024×768 container using the CSS property image-rendering: pixelated;. This is the magic trick that preserves the sharp, blocky look of the pixels without any blurring, making it feel authentically retro.

All the visual assets—floor tiles, wall textures, furniture, and character sprites—are packed into a single 24KB PNG sprite sheet. The rendering engine reads from this sheet, drawing small rectangular sections of it onto the canvas. The room itself is a simple 16x12 grid of tiles.

The layout isn't static; it's procedurally generated from your data on each load. The logic is straightforward:

  • The last 7 days of GitHub commits are fetched. For each commit, a book sprite is added to a bookshelf tile.
  • Your iCal feed is parsed for today's meetings. Each meeting spawns a chair in the "meeting area" of the room.
  • If you connect a source like Linear, each open issue assigned to you places a sticky note sprite on the wall.

The result is a room that is uniquely yours, changing day-to-day as your work progresses. Here's a simplified view of the data-to-canvas pipeline:

[GitHub API]--+      +--> [Tile List] ----+
              |      |                     |
[iCal Feed]---+------> [Object Placement]---> [Canvas Draw Loop] -> <canvas>
              |      |      Engine         |
[Conway API]--+      +--> [NPC List] -----+

The core of the rendering engine works with a simple data structure. Each object in the room, from a floor tile to a dynamic sprite, is represented by a similar object.

// A single object to be drawn to the canvas
interface Renderable {
  // The x, y position on the 256x192 canvas grid
  x: number;
  y: number;

  // The z-index for layering (e.g., player > floor)
  layer: number;

  // Coordinates on the master sprite sheet PNG
  spriteX: number;
  spriteY: number;

  // Dimensions of the sprite on the sheet
  spriteWidth: number;
  spriteHeight: number;
}

NPCs (the part that went viral)

The feature that truly made Star-Office take off was the Non-Player Characters (NPCs). I realized that work isn't just about tasks and commits; it's about the people you collaborate with. So, I decided to represent them in the office.

The app pulls the commit history for your main repositories over the last 30 days and identifies your frequent collaborators. For each person you've co-authored a commit with, an NPC is spawned in the room. Each NPC is assigned a unique, randomly generated sprite from a set of 18 character templates.

These NPCs don't just stand still. They follow a simple random walk algorithm, moving one tile every few seconds, which makes the room feel alive and bustling. The real magic happens on interaction. When you hover your mouse over a collaborator's NPC, a small tooltip appears showing their name and the message from their most recent commit. Even better, if they've pushed a commit to a shared repository within the last hour, a speech bubble appears above their head saying "just shipped!".

This small feature transformed the dashboard from a personal status screen into a shared, ambient awareness tool. It created a sense of connection and presence with your team. When I launched on Hacker News, the screenshots that got shared over and over were the ones showing a room full of coworker NPCs with their commit messages. It was the human element that resonated.

Agent NPCs (v0.3)

As I used Star-Office myself, I wanted to visualize another part of my modern workflow: autonomous AI agents. I'm a heavy user of Conway, an open-source automaton framework, for running background tasks. These agents often work on long-running jobs, and I had no easy way to see their status at a glance.

In version 0.3, I added an integration. If you set a CONWAY_URL environment variable in your self-hosted instance, Star-Office will poll that endpoint for active agent tasks assigned to you. For each pending task, it spawns a small robot NPC in the corner of the office.

This wasn't just a static icon. The robot's animation speed is tied to the agent's state. If the agent is actively processing (in a loop, making API calls), the robot's legs move quickly. If it's idle or waiting for a trigger, the robot stands still, occasionally blinking its single red eye. This provides subtle, ambient feedback on what your digital assistants are doing. Clicking on the robot sprite opens the agent's task journal directly in a new tab, bridging the gap between visualization and action. It made my agents feel less like abstract background processes and more like little helpers pottering around in my digital office.

Why Svelte 5 and not React

Choosing the right tool for the job was critical, especially with the strict performance budget. While I'm proficient in React, I chose Svelte 5 for this project, and it was absolutely the right call.

The primary reason was Svelte 5's new reactivity model, "runes". The core of Star-Office is an imperative canvas drawing loop. I need to calculate the positions of hundreds of tiles and sprites based on reactive data from APIs. In React, this would have been a tangled mess of useEffect, useMemo, and useRef to manage the canvas context and prevent re-renders from thrashing the DOM. With Svelte 5's $state and $effect runes, the code is beautifully simple. I declare my data sources as state, and the effect that draws to the canvas re-runs automatically and efficiently whenever the data changes. It felt like writing vanilla JavaScript, but with a powerful, fine-grained reactivity system built in.

The second reason was bundle size. A minimal React app (with react and react-dom) starts at around 45KB gzipped. A minimal Svelte 5 app is closer to 12KB. That 33KB difference is massive when your total budget is under 300KB. It gave me more room for application logic, parsing libraries, and the sprite assets themselves without compromising on the initial load time. For a tool designed to be a lightweight, always-open tab, every kilobyte counts.

What broke

No launch is perfectly smooth. Star-Office hit a few walls, and each one was a learning experience.

  • GitHub API Rate Limits: On launch day, the Hacker News traffic was immense. The app made unauthenticated requests to the GitHub API, and within an hour, my Vercel deploy IP was hitting the 60 requests/hour limit, breaking the app for everyone. The quick fix was to require users to provide a Personal Access Token (PAT) with repo scope. I made it clear the token was only stored in their browser's localStorage and never sent to a server. I also implemented a 15-minute client-side cache using Zustand to reduce redundant API calls.
  • iCal Caching: I discovered that Google Calendar's iCal feeds can be aggressively cached. Sometimes, a newly added event wouldn't show up in the feed for 5-10 minutes. Users were confused, thinking the app was broken. The fix was purely UI: I added a small "last synced: 2 minutes ago" pill next to the calendar area, making the data's potential staleness transparent.
  • Safari Rendering: The image-rendering: pixelated; CSS rule has inconsistent support. Safari, in particular, sometimes applied a slight blur. The fix was a two-parter: a CSS fallback using image-rendering: -webkit-crisp-edges; and providing a 2x resolution sprite sheet for browsers that might ignore the rule.
  • User-Generated Content: An early, naive version allowed users to upload their own sprite sheets via a URL parameter. It was a mistake. Within a day, someone created a shared link with a NSFW sprite sheet and posted it. I immediately pulled the feature and replaced it with a curated set of unlockable, pre-approved sprite packs.

Why it got traction

Looking back, the project's modest success came down to a few key factors.

First and foremost, it was inherently shareable. People could take a screenshot of their office, full of their work and their colleagues, and post it on Twitter or in their company Slack. This visual, personalized output was the single most powerful growth vector. I completely underestimated how much people enjoy seeing their own data reflected in a novel way.

Second, the "coworker NPC" feature was a Trojan horse for team adoption. An individual developer would try it, see their teammates walking around, and share it with their lead. Several team leads reached out to ask about self-hosting it for their entire team, which led to the creation of the self-host template. It went from a personal dashboard to a lightweight, ambient team presence tool.

Finally, the launch strategy on Hacker News was minimalist. I resisted the urge to write a long "Why I built this" article. The post was just a GIF, a one-sentence summary, and the link. This low-friction presentation let the project speak for itself and likely contributed to its quick climb to the #7 spot on the front page.

Design decisions I'd defend

I made several architectural choices that I believe were key to the project's spirit and success, even if they go against conventional wisdom.

  • No authentication backend: Storing the GitHub PAT in localStorage is a security trade-off, but it was the right one for this project. It eliminated the need for a server, a database, and an entire OAuth flow, reducing the project's complexity by an order of magnitude. A clear warning in the UI about the token's scope and storage location was sufficient.
  • No database: The application is completely stateless. All data is fetched from the source APIs on page load and held in memory. The 15-minute cache is the only persistence. This makes the application incredibly simple to host and maintain. There's no data to migrate, no database to manage. It's ephemeral by design.
  • No monetization: I was repeatedly asked about a "Pro" version. I've intentionally avoided any attempt at monetization. Star-Office is a toy. The moment I start charging for it, it becomes a product. The pressure to add features for paying customers would compromise the project's simplicity and charm. Keeping it free and open-source preserves its soul.
  • MIT License: I chose the most permissive license possible and made all the assets (sprite sheets, etc.) public. This was deliberate. I wanted people to fork it, play with it, and build their own versions. Seeing other developers create custom skins and data integrations has been one of the most rewarding parts of the journey.

What's next

While I'm committed to keeping the core experience simple, I have a few ideas for the future that align with the project's spirit.

  • Obsidian Integration: A plugin that reads your Obsidian vault. Each note you've created or edited today would appear as a book on the shelf. Clicking the book would deep-link directly to the note in your vault.
  • Multiplayer Mode: A more formal version of the shared office concept. Using a lightweight peer-to-peer service or a simple WebSocket server, teams could share a single, persistent room where they could see each other's avatars and statuses in real-time.
  • 3D Fallback Renderer: For users on high-DPI "retina" screens, the pixelated aesthetic can sometimes feel too small. I'm experimenting with a toggle that would switch to a simple, low-poly 3D renderer that maintains the blocky, retro feel but in a 2.5D perspective, making it more legible on modern displays.