Files
GearBox/.planning/research/FEATURES.md

17 KiB
Raw Blame History

Feature Research

Domain: Gear management — candidate comparison, setup impact preview, and candidate ranking Researched: 2026-03-16 Confidence: HIGH (existing codebase fully understood; UX patterns verified via multiple sources)


Context

This is a subsequent milestone research file for v1.3 Research & Decision Tools. The features below are additive to v1.2. All three features operate within the existing threads/$threadId page and its data model.

Existing data model relevant to this milestone:

  • threadCandidates: id, threadId, name, weightGrams, priceCents, categoryId, notes, productUrl, imageFilename, status — no rank, pros, or cons columns yet
  • setups + setupItems: stores weight/cost per setup item with classification (base/worn/consumable)
  • getSetupWithItems already returns classification per item — available for impact preview

Feature Landscape

Table Stakes (Users Expect These)

Features users assume exist in any comparison or decision tool. Missing these makes the thread detail page feel incomplete as a decision workspace.

Feature Why Expected Complexity Notes
Side-by-side comparison view Any comparison tool in any domain shows attributes aligned per-column. Card grid (current) forces mental juggling between candidates. E-commerce, spec sheets, gear apps — all use tabular layout for comparison. MEDIUM Rows = attributes (image, name, weight, price, status, notes, link), columns = candidates. Sticky attribute-label column during horizontal scroll. Max 34 candidates usable on desktop; 2 on mobile. Toggle between grid view (current) and table view.
Weight delta per candidate Gear apps (LighterPack, GearGrams) display weight totals prominently. Users replacing an item need the delta, not just the raw weight of the candidate. LOW Pure client-side computation: candidate.weightGrams - existingItemWeight. No API call needed if setup data already loaded via useSetup.
Cost delta per candidate Same reasoning as weight delta. A purchase decision is always the weight vs. cost tradeoff. LOW Same pattern as weight delta. Color-coded: green for savings/lighter, red for more expensive/heavier.
Setup selector for impact preview User needs to pick which setup to compute deltas against — not all setups contain the same category of item being replaced. MEDIUM Dropdown of setup names populated from useSetups(). When selected, loads setup via useSetup(id). "No setup selected" state shows raw candidate values only, no delta.

Differentiators (Competitive Advantage)

Features not found in LighterPack, GearGrams, or any other gear app. Directly serve the "decide between candidates" workflow that is unique to GearBox.

Feature Value Proposition Complexity Notes
Drag-to-rank ordering Makes priority explicit without a numeric input. Ranking communicates "this is my current top pick." Maps to how users mentally stack-rank options during research. No competitor has this in the gear domain. MEDIUM @dnd-kit/sortable is the current standard (actively maintained; react-beautiful-dnd is abandoned as of 2025). Requires new rank integer column on threadCandidates. Persist order via PATCH endpoint.
Per-candidate pros/cons fields Freeform text capturing the reasoning behind ranking. LighterPack and GearGrams have notes per item but no structured decision rationale. Differentiates GearBox as a decision tool, not just a list tracker. LOW Two textarea fields per candidate. New pros and cons text columns on threadCandidates. Visible in comparison view rows and candidate edit panel.
Impact preview with category-matched delta Setup items have a category. The most meaningful delta is weight saved within the same category (e.g., comparing sleeping pads, subtract current sleeping pad weight from setup total). More actionable than comparing against the entire setup total. MEDIUM Use candidate.categoryId to find matching setup items and compute delta. Edge case: no item of that category in the setup → show "not in setup." Data already available from getSetupWithItems.

Anti-Features (Commonly Requested, Often Problematic)

Feature Why Requested Why Problematic Alternative
Custom comparison attributes "I want to compare battery life, durability, color..." PROJECT.md explicitly rejects this as a complexity trap. Custom attributes require schema generalization, dynamic rendering, and data entry friction for every candidate. Notes field and pros/cons fields cover the remaining use cases.
Score/rating calculation Automatically rank candidates by computed score Score algorithms require encoding the user's weight-vs-price preference — personalization complexity. Users distrust opaque scores. Manual drag-to-rank expresses the user's own weighting without encoding it in an algorithm.
Side-by-side comparison across threads Compare candidates from different research threads Candidates belong to different purchase decisions — mixing them is conceptually incoherent. Different categories are never apples-to-apples. Thread remains the scope boundary. Cross-thread planning is what setups are for.
Comparison permalink/share Share a comparison view URL GearBox is single-user, no auth for v1. Sharing requires auth, user management, public/private visibility. Out of scope for v1 per PROJECT.md. Future feature.
Classification-aware impact preview as MVP requirement Show delta broken down by base/worn/consumable While data is available, the classification breakdown adds significant UI complexity. The flat delta answers "will this make my setup lighter?" which is 90% of the use case. Flat delta for MVP. Classification-aware breakdown as a follow-up enhancement (P2).

Feature Dependencies

[Side-by-side comparison view]
    └──requires──> [All candidate fields visible in UI]
                   (weightGrams, priceCents, notes, productUrl already in schema)
    └──enhances──> [Pros/cons fields] (displayed as comparison rows)
    └──enhances──> [Drag-to-rank] (rank number shown as position in comparison columns)
    └──enhances──> [Impact preview] (delta displayed per-column inline)

[Impact preview (weight + cost delta)]
    └──requires──> [Setup selector] (user picks which setup to compute delta against)
    └──requires──> [Setup data client-side] (useSetup hook already exists, no new API)
    └──requires──> [Candidate weight/price data] (already in threadCandidates schema)

[Setup selector]
    └──requires──> [useSetups() hook] (already exists in src/client/hooks/useSetups.ts)
    └──requires──> [useSetup(id) hook] (already exists, loads items with classification)

[Drag-to-rank]
    └──requires──> [rank INTEGER column on threadCandidates] (new — schema migration)
    └──requires──> [PATCH /api/threads/:id/candidates/rank endpoint] (new API endpoint)
    └──enhances──> [Side-by-side comparison] (rank visible as position indicator)
    └──enhances──> [Card grid view] (rank badge on each CandidateCard)

[Pros/cons fields]
    └──requires──> [pros TEXT column on threadCandidates] (new — schema migration)
    └──requires──> [cons TEXT column on threadCandidates] (new — schema migration)
    └──requires──> [updateCandidateSchema extended] (add pros/cons to Zod schema)
    └──enhances──> [CandidateForm edit panel] (new textarea fields)
    └──enhances──> [Side-by-side comparison] (pros/cons rows in comparison table)

Dependency Notes

  • Side-by-side comparison is independent of schema changes. It can be built using existing candidate data. No migrations required. Delivers value immediately.
  • Impact preview is independent of schema changes. Uses existing useSetups and useSetup hooks client-side. Delta computation is pure math in the component. No new API endpoint needed for MVP.
  • Drag-to-rank requires schema migration. rank column must be added to threadCandidates. Default ordering on migration = createdAt ascending.
  • Pros/cons requires schema migration. Two nullable text columns on threadCandidates. Low risk — nullable, backwards compatible.
  • Comparison view enhances everything. Best delivered after rank and pros/cons schema work is done so the full table is useful from day one.

MVP Definition

Launch With (v1.3 milestone)

  • Side-by-side comparison view — Core deliverable. Replace mental juggling of the card grid with a scannable table. No schema changes. Highest ROI, lowest risk.
  • Impact preview: flat weight + cost delta per candidate — Shows +/- X g and +/- $Y vs. the selected setup. Pure client-side math. No schema changes.
  • Setup selector — Dropdown of user's setups. Required for impact preview. One interaction: pick a setup, see deltas update.
  • Drag-to-rank — Requires rank column migration. @dnd-kit/sortable handles the drag UX. Persist via new PATCH endpoint.
  • Pros/cons text fields — Requires pros + cons column migration. Trivially low implementation complexity once schema is in place.

Add After Validation (v1.x)

  • Classification-aware impact preview — Delta broken down by base/worn/consumable. Higher complexity UI. Add once flat delta is validated as useful. Trigger: user feedback requests "which classification does this affect?"
  • Rank indicator on card grid — Small "1st", "2nd" badge on CandidateCard. Trigger: users express confusion about which candidate is ranked first without entering comparison view.
  • Comparison view on mobile — Horizontal scroll works but is not ideal. Consider attribute-focus swipe view. Trigger: usage data shows mobile traffic on thread pages.

Future Consideration (v2+)

  • Comparison permalink — Requires auth/multi-user work first.
  • Auto-fill from product URL — Fragile scraping, rejected in PROJECT.md.
  • Custom comparison attributes — Explicitly rejected in PROJECT.md.

Feature Prioritization Matrix

Feature User Value Implementation Cost Priority
Side-by-side comparison view HIGH MEDIUM P1
Setup impact preview (flat delta) HIGH LOW P1
Setup selector for impact preview HIGH LOW P1
Drag-to-rank ordering MEDIUM MEDIUM P1
Pros/cons text fields MEDIUM LOW P1
Classification-aware impact preview MEDIUM HIGH P2
Rank indicator on card grid LOW LOW P2
Mobile-optimized comparison view LOW MEDIUM P3

Priority key:

  • P1: Must have for this milestone launch
  • P2: Should have, add when possible
  • P3: Nice to have, future consideration

Competitor Feature Analysis

Feature LighterPack GearGrams OutPack Our Approach
Side-by-side candidate comparison None (list only) None (library + trip list) None Inline comparison table on thread detail page, toggle from grid view
Impact preview / weight delta None (duplicate lists manually to compare) None (no delta concept) None Per-candidate delta vs. selected setup, computed client-side
Candidate ranking None None None Drag-to-rank with persisted rank column
Pros/cons annotation None (notes field only) None (notes field only) None Dedicated pros and cons fields separate from general notes
Status tracking None "wish list" item flag only None Already built in v1.2 (researching/ordered/arrived)

Key insight: No existing gear management tool has a comparison view, delta preview, or ranking system for candidates within a research thread. This is an unmet-need gap. The features are adapted from general product comparison UX (e-commerce) to the gear domain.


Implementation Notes by Feature

Side-by-side Comparison View

  • Rendered as a transposed table: rows = attribute labels, columns = candidates.
  • Rows: Image (thumbnail), Name, Weight, Price, Status, Notes, Link, Pros, Cons, Rank, Impact Delta (weight), Impact Delta (cost).
  • Sticky first column (attribute label) while candidate columns scroll horizontally for 3+.
  • Candidate images at reduced aspect ratio (square thumbnail ~80px).
  • Weight/price cells use existing formatWeight / formatPrice formatters with the user's preferred unit.
  • Status cell reuses existing StatusBadge component.
  • "Pick as winner" action available per column (reuses existing openResolveDialog).
  • Toggle between grid view (current) and table view. Preserve both modes. Default to grid; user activates comparison mode explicitly.
  • Comparison mode is a UI state only (Zustand or local component state) — no URL change needed.

Impact Preview

  • Setup selector: <select> or custom dropdown populated from useSetups().
  • On selection: load setup via useSetup(id). Compute delta per candidate: candidate.weightGrams - matchingCategoryWeight where matchingCategoryWeight is the sum of setup item weights in the same category as the thread.
  • Delta display: colored pill on each candidate column in comparison view:
    • Negative delta (lighter) = green, prefixed with ""
    • Positive delta (heavier) = red, prefixed with "+"
    • Zero = neutral gray
  • Same pattern for cost delta.
  • "No setup selected" state = no delta row shown.
  • "Category not in setup" state = "not in setup" label instead of delta.
  • No new API endpoints required. All data is client-side once setups are loaded.

Drag-to-Rank

  • @dnd-kit/sortable with SortableContext wrapping the candidate list.
  • useSortable hook per candidate with a drag handle (Lucide grip-vertical icon).
  • Drag handle visible always (not hover-only) so the affordance is clear.
  • On onDragEnd: recompute ranks using arrayMove(), call PATCH /api/threads/:threadId/candidates/rank with { orderedIds: number[] }.
  • Server endpoint: bulk update rank for each candidate ID in the thread atomically.
  • rank column: INTEGER nullable. Null = unranked (treated as lowest rank). Default to createdAt order on first explicit rank save.
  • Rank number badge: displayed on each CandidateCard corner (small gray circle, "1", "2", "3").
  • Works in both grid view and comparison view.

Pros/Cons Fields

  • Two textarea inputs added to the existing CandidateForm (slide-out panel).
  • Labels: "Pros" and "Cons" — plain text, no icons.
  • Displayed below the existing Notes field in the form.
  • Extend updateCandidateSchema with pros: z.string().optional() and cons: z.string().optional().
  • In comparison table: pros and cons rows display as plain text, line-wrapped.
  • In card grid: pros/cons not shown on card surface (too much density). Visible only in edit panel and comparison view.

Schema Changes Required

Two schema migrations needed for this milestone:

-- Migration 1: Candidate rank
ALTER TABLE thread_candidates ADD COLUMN rank INTEGER;

-- Migration 2: Candidate pros/cons
ALTER TABLE thread_candidates ADD COLUMN pros TEXT;
ALTER TABLE thread_candidates ADD COLUMN cons TEXT;

Both columns are nullable and backwards compatible. Existing candidates get NULL values. UI treats NULL rank as unranked, NULL pros/cons as empty string.


Sources


Feature research for: GearBox v1.3 — candidate comparison, setup impact preview, candidate ranking Researched: 2026-03-16