Files
GearBox/.planning/research/FEATURES.md

263 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```sql
-- 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
- [Designing The Perfect Feature Comparison Table — Smashing Magazine](https://www.smashingmagazine.com/2017/08/designing-perfect-feature-comparison-table/) — table layout patterns, sticky headers, progressive disclosure (HIGH confidence)
- [Comparison Tables for Products, Services, and Features — Nielsen Norman Group](https://www.nngroup.com/articles/comparison-tables/) — information architecture for comparison, anti-patterns (HIGH confidence)
- [The Ultimate Drag-and-Drop Toolkit for React: @dnd-kit — BrightCoding (2025)](https://www.blog.brightcoding.dev/2025/08/21/the-ultimate-drag-and-drop-toolkit-for-react-a-deep-dive-into-dnd-kit/) — confirmed dnd-kit as current standard, react-beautiful-dnd abandoned (HIGH confidence)
- [dnd-kit Sortable Docs](https://docs.dndkit.com/presets/sortable) — SortableContext, useSortable, arrayMove patterns (HIGH confidence)
- [Ultralight: The Gear Tracking App I'm Leaving LighterPack For — TrailsMag](https://trailsmag.net/blogs/hiker-box/ultralight-the-gear-tracking-app-i-m-leaving-lighterpack-for) — LighterPack feature gap analysis (MEDIUM confidence)
- [Comparing products: UX design best practices — Contentsquare](https://contentsquare.com/blog/comparing-products-design-practices-to-help-your-users-avoid-fragmented-comparison-7/) — fragmented comparison UX pitfalls (HIGH confidence)
- [Drag and drop UI examples and UX tips — Eleken](https://www.eleken.co/blog-posts/drag-and-drop-ui) — drag affordance and visual feedback patterns (MEDIUM confidence)
- GearBox codebase analysis (src/db/schema.ts, src/server/services/, src/client/hooks/) — confirmed existing data model, no rank/pros/cons columns present (HIGH confidence)
---
*Feature research for: GearBox v1.3 — candidate comparison, setup impact preview, candidate ranking*
*Researched: 2026-03-16*