# Nodeger

> One screen replaces calendar, task tracker, and timer. Drag-and-drop task nodes on a 30-day grid.
>
> https://pravda.systems/work/nodeger · 2026-06-13

**Problem.** Planning multi-day work means juggling three tools (a calendar, a task tracker, and a timer) and re-entering the same data in each.

**Solution.** One browser canvas where tasks are draggable, resizable, connectable nodes with live timers on a 30-day grid.

**Outcome.** A working prototype with a viewport-culling renderer built to hold 200+ nodes at a 60fps target, browser auto-save, and its own honest debt audit on the record. Built in 5 days.

## Key metrics

- **88** Source files — Full front-end application including hooks, stores, components, and feature modules. Excludes .vite build cache and graphify-out directories present in the repo. _(verified: find . excluding .git/.vite/graphify-out/node_modules/public, counted 88 files at HEAD)_
- **~14.6k lines** TypeScript / JavaScript lines — TypeScript/TSX source lines in src/ (.ts: 6,485 + .tsx: 8,126 = 14,611 TypeScript, plus ~109 lines in root .js utilities). The stats.json .js total of 31,264 is build-cache files in .vite/, not source. _(verified: Counted via find src -name '*.ts' -o -name '*.tsx' | xargs wc -l (14,540 total). Root .js files: 109 lines)_
- **51 commits** Commits — Built and iterated in 5 days (2025-08-30 to 2025-09-03) _(verified: stats.json, commits)_
- **5 days** Build window — First commit to last commit: 2025-08-30 through 2025-09-03 _(verified: stats.json, first_commit / last_commit)_
- **1,000 cached paths** Connection path cache limit — FastConnections.tsx caches bezier path strings. LRU rotation at limit _(verified: src/features/nodes/components/FastConnections.tsx, pathCache size limit)_

## The problem

**Planning multi-day work forces you across three apps and re-entering the same data in each.** A calendar tracks dates, a task tracker tracks ownership, a timer tracks effort. None of them talk. Standard tools also lock every task into an identical box, so scope, dependencies, and who-owns-what never show on one screen.

## What it delivers

**One screen shows the entire status of a multi-day project (scope, progress, owner, and dependencies) with no app-switching.** Each task is a node you drag, resize, and connect on a **30-day grid**. Nodes span multiple days, carry live timers, and link with bezier lines. Color tags assign owners at a glance. Node height signals scope, timer state signals progress, lines signal dependencies.

## How it works

**An event bus keeps every feature fully decoupled, so any part can be replaced or tested in isolation.** The calendar and node systems never import each other. They talk only through typed events like `canvas:click` and `node:move`, with state in per-feature Zustand stores. A **virtual renderer** draws only on-screen nodes (plus a 20% buffer), built to hold **200+ nodes at a 60fps target**. Four performance modes trade visual effects for speed on demand, and work auto-saves to the browser on a short debounce.

## One component, every node type

**Adding a new node type means writing a config object, not a new component.** Task, Description, and Backdrop nodes all render from a single master component driven by a type definition. Consolidating to this pattern removed **1,200+ lines of duplicate code** and cut duplication from ~20% to under 5%.

## Honest engineering, on the record

**Every tradeoff and known weakness is documented in the repo, written by Danylo, not a reviewer.** The README names the exact gaps: a large node store to split, console logs to strip, and 0% test coverage, alongside a dated refactoring roadmap. Each feature folder ships an AI modification guide marking what is safe to change. This is a prototype built for fast iteration, with the debt accounted for rather than hidden.

## Features

- **Calendar grid canvas** — Tasks live on a 30-day timeline with per-day expandable rows and grid-snapping placement.
- **Rubber-band resize** — Nodes resize with elastic resistance, snap magnetically to the grid within 20px, and settle with a spring animation.
- **Node timer system** — Each node carries its own play/pause/finish timer with accumulated time and long-press shortcuts.
- **Visual connections** — Drag any node's port to another to show a dependency, drawn as a cached bezier or straight line.
- **Virtual rendering** — Only on-screen nodes render (plus a 20% buffer), so the canvas is built to stay near its 60fps target with 200+ nodes in play.
- **Performance modes** — Four modes progressively strip blur, shadows, and animations to trade appearance for frame rate.

## Architecture

Feature-per-directory layout. Core module (events, store, registry, types) is stable after initial setup. Features communicate only through the Mitt event bus, with no direct imports between feature directories. Each feature registers itself and exposes an events.ts file for its handlers.

- **Core / Event bus** — Mitt singleton. All cross-feature communication goes through typed events
- **Calendar feature** — 30-day grid, row expansion, cell coordinate system, canvas click events
- **Nodes feature** — Master node component, drag/resize hooks, timer state machine, collision resolution, virtual renderer
- **Nodes / Connections subsystem** — Dynamic input/output ports per node, bezier path rendering, path cache, drag-to-connect (FastConnections.tsx lives inside the nodes feature directory)
- **Performance settings** — Four rendering modes persisted to localStorage. Controls will-change, blur, animation toggles

## Tech

- **Languages:** TypeScript, JavaScript, CSS
- **Frameworks:** React 19, Zustand, Framer Motion, Vite
- **Infrastructure:** localStorage (persistence), RAF throttling (drag/resize)

## Skills demonstrated

- Builds interactive web tools that replace several apps with one
- Keeps the UI fast under heavy load (renderer built for 200+ nodes)
- Designs features that stay decoupled and cheap to change
- Cuts duplicate code through reusable, config-driven components
- Ships fast (working prototype in 5 days) without overselling it
- Documents tradeoffs and debt honestly for clean handoff
- React, TypeScript, and Zustand state management
