Files
GearBox/.planning/research/STACK.md

12 KiB

Stack Research -- v1.3 Research & Decision Tools

Project: GearBox Researched: 2026-03-16 Scope: Stack additions for side-by-side candidate comparison, setup impact preview, and drag-to-reorder candidate ranking with pros/cons Confidence: HIGH

Key Finding: Zero New Dependencies

All three v1.3 features are achievable with the existing stack. The drag-to-reorder feature, which would normally require a dedicated DnD library, is covered by framer-motion's built-in Reorder component — already installed at v12.37.0 with React 19 support confirmed.

No New Dependencies Required

Feature Library Needed Status
Side-by-side comparison view None — pure layout/UI Existing Tailwind CSS
Setup impact preview None — SQL delta calculation Existing Drizzle ORM + TanStack Query
Drag-to-reorder candidates Reorder component Already in framer-motion@12.37.0
Pros/cons text fields None — schema + form Existing Drizzle + Zod + React

How Each Feature Uses the Existing Stack

1. Side-by-Side Candidate Comparison

No schema changes. No new dependencies.

Existing Tech How It Is Used
Tailwind CSS v4 Responsive comparison table layout. Horizontal scroll on mobile with overflow-x-auto. Fixed first column (row labels) using sticky left-0.
TanStack Query (useThread) Thread detail already fetches all candidates in one query. Comparison view reads from the same cached data — no new API endpoint.
Lucide React Comparison row icons (weight, price, status, link). Already in the curated icon set.
formatWeight / formatPrice Existing formatters handle display with selected unit/currency. No changes needed.
useWeightUnit / useCurrency Existing hooks provide formatting context. Comparison view uses them identically to CandidateCard.

Implementation approach: Add a view toggle (grid vs. comparison table) to the thread detail page. The comparison view is a <table> or CSS grid with candidates as columns and attributes as rows. Data already lives in useThread response — no API changes.

2. Setup Impact Preview

No new dependencies. Requires one new API endpoint.

Existing Tech How It Is Used
Drizzle ORM New query: for a given setup, sum weight_grams and price_cents of its items. Then compute delta against each candidate's weight_grams and price_cents. Pure arithmetic in the service layer.
TanStack Query New useSetupImpact(threadId, setupId) hook fetching GET /api/threads/:threadId/impact?setupId=X. Returns array of { candidateId, weightDelta, costDelta }.
Hono + Zod validator New route validates setupId query param. Delegates to service function.
formatWeight / formatPrice Format deltas with + prefix for positive values (candidate adds weight/cost) and - for negative (candidate is lighter/cheaper than what's already in setup).
useSetups hook Existing useSetups() provides the setup list for the picker dropdown.

Delta calculation logic (server-side service):

// For each candidate in thread:
// weightDelta = candidate.weightGrams - (matching item in setup).weightGrams
// If no matching item in setup (it would be added, not replaced): delta = candidate.weightGrams

// A "matching item" means: item in setup with same categoryId as the thread.
// This is the intended semantic: "how does picking this candidate affect my setup?"

Key decision: Impact preview is read-only and derived. It does not mutate any data. It computes what would happen if the candidate were picked, without modifying the setup. The delta is displayed inline on each candidate card or in the comparison view.

3. Drag-to-Reorder Candidate Ranking with Pros/Cons

No new DnD library. Requires schema changes.

Existing Tech How It Is Used
framer-motion@12.37.0 Reorder Reorder.Group wraps the candidate list. Reorder.Item wraps each candidate card. onReorder updates local order state. onDragEnd fires the persist mutation.
Drizzle ORM Two new columns on thread_candidates: sortOrder integer (default 0, lower = higher rank) and pros text / cons text (nullable).
TanStack Query mutation usePatchCandidate for pros/cons text updates. useReorderCandidates for bulk sort order update after drag-end.
Hono + Zod validator PATCH /api/threads/:threadId/candidates/reorder accepts { candidates: Array<{ id, sortOrder }> }. PATCH /api/candidates/:id accepts { pros?, cons? }.
Zod Extend updateCandidateSchema with pros: z.string().nullable().optional(), cons: z.string().nullable().optional(), sortOrder: z.number().int().optional().

Framer Motion Reorder API pattern:

import { Reorder } from "framer-motion";

// State holds candidates sorted by sortOrder
const [orderedCandidates, setOrderedCandidates] = useState(
  [...candidates].sort((a, b) => a.sortOrder - b.sortOrder)
);

// onReorder fires continuously during drag — update local state only
// onDragEnd fires once on drop — persist to DB
<Reorder.Group
  axis="y"
  values={orderedCandidates}
  onReorder={setOrderedCandidates}
>
  {orderedCandidates.map((candidate) => (
    <Reorder.Item
      key={candidate.id}
      value={candidate}
      onDragEnd={() => persistOrder(orderedCandidates)}
    >
      <CandidateCard ... />
    </Reorder.Item>
  ))}
</Reorder.Group>

Schema changes required:

Table Column Type Default Purpose
thread_candidates sort_order integer NOT NULL 0 Rank position (lower = higher rank)
thread_candidates pros text NULL Free-text pros annotation
thread_candidates cons text NULL Free-text cons annotation

Sort order persistence pattern: On drag-end, send the full reordered array with new sortOrder values (0-based index positions). Backend replaces existing sort_order values atomically. This is the same delete-all + re-insert pattern used for setupItems but as an UPDATE instead.

Installation

# No new packages. Zero.

All required capabilities are already installed.

Alternatives Considered

Drag and Drop: Why Not Add a Dedicated Library?

Option Version React 19 Status Verdict
framer-motion Reorder (already installed) 12.37.0 React 19 explicit peerDep (`^18.0.0
@dnd-kit/core + @dnd-kit/sortable 6.3.1 No React 19 support (stale ~1yr, open GitHub issue #1511) AVOID
@dnd-kit/react (new rewrite) 0.3.2 React 19 compatible Pre-1.0, no maintainer ETA on stable
@hello-pangea/dnd 18.0.1 No React 19 (stale ~1yr, peerDep ^18.0.0 only) AVOID
pragmatic-drag-and-drop latest Core is React-agnostic but some sub-packages missing React 19 Overkill for a single sortable list
Custom HTML5 DnD N/A N/A 200+ lines of boilerplate, worse accessibility

Why framer-motion Reorder wins: Already in the bundle. React 19 peer dep confirmed in lockfile. Handles the single use case (vertical sortable list) with 10 lines of code. Provides smooth layout animations at zero additional cost. The limitation (no cross-container drag, no multi-row grid) does not apply — candidate ranking is a single vertical list.

Why not @dnd-kit: The legacy @dnd-kit/core@6.3.1 has no official React 19 support and has been unmaintained for ~1 year. The new @dnd-kit/react@0.3.2 does support React 19 but is pre-1.0 with zero maintainer response on stability/roadmap questions (GitHub Discussion #1842 has 0 replies). Adding a pre-1.0 library when the project already has a working solution is unjustifiable.

Setup Impact: Why Not Client-Side Calculation?

Client-side delta calculation (using cached React Query data) is simpler to implement but:

  • Requires loading both the full setup items list AND all candidates into the client
  • Introduces staleness bugs if setup items change in another tab
  • Is harder to test (service test vs. component test)

Server-side calculation in a service function is testable, authoritative, and consistent with the existing architecture (services compute aggregates, not components).

What NOT to Add

Avoid Why Use Instead
@dnd-kit/core + @dnd-kit/sortable No React 19 support, stale for ~1 year (latest 6.3.1 from 2024) framer-motion Reorder (already installed)
@hello-pangea/dnd No React 19 support, peerDep react: "^18.0.0" only, stale framer-motion Reorder
react-comparison-table or similar component packages Fragile third-party layouts for a simple table. Custom Tailwind table is trivial and design-consistent. Custom Tailwind CSS table layout
Modal/dialog library (Radix, Headless UI) The project already has a hand-rolled modal pattern (SlideOutPanel, ConfirmDialog). Adding a library for one more dialog adds inconsistency. Extend existing modal patterns
Rich text editor for pros/cons Markdown editors are overkill for a single-line annotation field. Users want a quick note, not a document. Plain <textarea> with Tailwind styling

Stack Patterns by Variant

If the comparison view needs mobile scroll:

  • Wrap comparison table in overflow-x-auto
  • Freeze the first column (attribute labels) with sticky left-0 bg-white z-10
  • This is pure CSS, no JavaScript or library needed

If the setup impact preview needs a setup picker:

  • Use useSetups() (already exists) to populate a <select> dropdown
  • Store selected setup ID in local component state (not URL params — this is transient UI)
  • No new state management needed

If pros/cons fields need to auto-save:

  • Use a debounced mutation (300-500ms) that fires on onChange
  • Or save on onBlur (simpler, adequate for this use case)
  • Existing useUpdateCandidate hook already handles candidate mutations — extend schema only

Version Compatibility

Package Version in Project React 19 Compatible Notes
framer-motion 12.37.0 YES — peerDeps `"^18.0.0
drizzle-orm 0.45.1 N/A (server-side) ALTER TABLE or migration for new columns
zod 4.3.6 N/A Extend existing schemas
@tanstack/react-query 5.90.21 YES New hooks follow existing patterns

Sources


Stack research for: GearBox v1.3 -- Research & Decision Tools Researched: 2026-03-16