This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
BucketLine is an Electron desktop app for visual label management in Linear. Each "bucket" is a Linear label; dragging an issue card between buckets updates its label in Linear instantly. Built with Electron + Vite + React + TypeScript + Tailwind CSS v4.
npm run dev # run in development (hot-reloads renderer)
npm test # run tests once (vitest)
npm run test:watch # run tests in watch mode
npm run test:coverage # run tests with coverage report
npm run lint # eslint
npm run typecheck # tsc for both main and renderer
npm run build # typecheck + electron-vite build (production)
npm run build:mac # build + package macOS DMG
npm run build:win # build + package Windows installer
npm run build:linux # build + package Linux AppImageTo run a single test file:
npx vitest run src/renderer/src/store/__tests__/appStore.test.tsThis is a standard Electron app with three processes:
Main process (src/main/)
index.ts— creates theBrowserWindow, wires up auto-updater, callsregisterIpcHandlers()ipc.ts— all IPC handlers. This is where@linear/sdkis used (never in renderer). The LinearLinearClientis instantiated fresh per call using the stored API key.electron-storepersists the API key (encrypted viasafeStoragewhen available), bucket layout positions per team, and last selected team.
Preload (src/preload/)
index.ts— exposes two globals viacontextBridge:window.electron(justprocess.platform) andwindow.linear(all IPC call wrappers). This is the only safe boundary between renderer and main.index.d.ts— canonical type definitions for all shared data shapes (LinearIssue,LinearLabel,LinearTeam,LinearState,BucketPosition, etc.) and thewindow.linearAPI surface. Import types from here; don't redefine them.
Renderer (src/renderer/src/)
- React app. All Linear calls go through
window.linear.*(the preload bridge) — never import@linear/sdkhere. store/appStore.ts— Zustand store. Single source of truth for: auth state, selected team, issues/labels, draft cards, filters, and auto-refresh coordination. Includes optimistic mutation helpers (addLabelToIssue,removeLabelFromIssue,rollbackIssueLabelChange) that update state immediately before the IPC round-trip completes.hooks/useLinear.ts— React hook wrapping all mutations (addLabel,removeLabel,moveToLabel) with optimistic updates and rollback on error. Also owns therefreshfunction which merges incoming issues without triggering a loading spinner, andsetDraggingwhich defers any pending refresh until a drag ends.components/BucketCanvas.tsx— the main canvas. Owns@dnd-kitdrag context,react-zoom-pan-pinchpan/zoom transform, bucket position state (loaded/saved via IPC), the overlap-prevention algorithm, and the auto-refresh 90s interval. Buckets are always[unlabeled, ...labelBuckets, draft].components/Bucket.tsx— individual draggable/resizable bucket column. Uses auseDroppablefrom dnd-kit; issues within are sortable.components/IssueCard.tsx— individual card. Draggable via dnd-kit; right-click opensContextMenu.
- IPC response convention: all IPC handlers return
{ data: T } | { error: string }. Check forresult.errorbefore usingresult.data. - electron-store bundling:
electron-storev11 is pure ESM, so it's excluded fromexternalizeDepsPlugininelectron.vite.config.tsand bundled by Vite instead. - Draft cards: local-only concept (not written to Linear). A draft card is a copy of an issue staged in the draft bucket. Dragging it to a label bucket calls
addLabelon the source issue and removes the draft. - Label update strategy:
updateIssueLabelsinipc.tsfetches livelabelIdsfrom Linear before applying add/remove, to avoid clobbering concurrent edits. - Theme system: CSS
data-themeattribute on<html>, set inApp.tsx, persisted tolocalStorage. Themes are defined as CSS variables; Tailwind classes liketext-accent-500resolve via those variables.
Tests use Vitest + jsdom + @testing-library/react. The global test setup (src/test-setup.ts) mocks all window.linear.* methods with vi.fn() so renderer tests don't need a real Electron process.
Coverage is configured for src/renderer/src/store/**, src/renderer/src/hooks/**, and src/main/ipc.ts.