Skip to content

Latest commit

 

History

History
61 lines (45 loc) · 4.55 KB

File metadata and controls

61 lines (45 loc) · 4.55 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

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.

Commands

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 AppImage

To run a single test file:

npx vitest run src/renderer/src/store/__tests__/appStore.test.ts

Architecture

This is a standard Electron app with three processes:

Main process (src/main/)

  • index.ts — creates the BrowserWindow, wires up auto-updater, calls registerIpcHandlers()
  • ipc.ts — all IPC handlers. This is where @linear/sdk is used (never in renderer). The Linear LinearClient is instantiated fresh per call using the stored API key. electron-store persists the API key (encrypted via safeStorage when available), bucket layout positions per team, and last selected team.

Preload (src/preload/)

  • index.ts — exposes two globals via contextBridge: window.electron (just process.platform) and window.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 the window.linear API 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/sdk here.
  • 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 the refresh function which merges incoming issues without triggering a loading spinner, and setDragging which defers any pending refresh until a drag ends.
  • components/BucketCanvas.tsx — the main canvas. Owns @dnd-kit drag context, react-zoom-pan-pinch pan/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 a useDroppable from dnd-kit; issues within are sortable.
  • components/IssueCard.tsx — individual card. Draggable via dnd-kit; right-click opens ContextMenu.

Key design decisions

  • IPC response convention: all IPC handlers return { data: T } | { error: string }. Check for result.error before using result.data.
  • electron-store bundling: electron-store v11 is pure ESM, so it's excluded from externalizeDepsPlugin in electron.vite.config.ts and 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 addLabel on the source issue and removes the draft.
  • Label update strategy: updateIssueLabels in ipc.ts fetches live labelIds from Linear before applying add/remove, to avoid clobbering concurrent edits.
  • Theme system: CSS data-theme attribute on <html>, set in App.tsx, persisted to localStorage. Themes are defined as CSS variables; Tailwind classes like text-accent-500 resolve via those variables.

Testing

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.