20 KiB
Pitfalls Research
Domain: Gear management, collection tracking, purchase planning (single-user web app) Researched: 2026-03-14 Confidence: HIGH (domain-specific patterns well-documented across gear community and inventory app space)
Critical Pitfalls
Pitfall 1: Unit Handling Treated as Display-Only
What goes wrong: Weight and price values are stored as bare numbers without unit metadata. The app assumes everything is grams or dollars, then breaks when users enter ounces, pounds, kilograms, or foreign currencies. Worse: calculations like "total setup weight" silently produce garbage when items have mixed units. A 200g tent and a 5lb sleeping bag get summed as 205.
Why it happens: In a single-user app it feels safe to skip unit handling -- "I'll just always use grams." But real product specs come in mixed units (manufacturers list in oz, g, kg, lb), and copy-pasting from product pages means mixed data creeps in immediately.
How to avoid: Store all weights in a canonical unit (grams) at write time. Accept input in any unit but convert on save. Store the original unit for display purposes but always compute on the canonical value. Build a simple conversion layer from day one -- it is 20 lines of code now vs. a data migration later.
Warning signs:
- Weight field is a plain number input with no unit selector
- No conversion logic exists anywhere in the codebase
- Aggregation functions (total weight) do simple
SUM()without unit awareness
Phase to address: Phase 1 (Data model / Core CRUD) -- unit handling must be in the schema from the start. Retrofitting requires migrating every existing item.
Pitfall 2: Rigid Category Hierarchy Instead of Flexible Tagging
What goes wrong: The app ships with a fixed category tree (Shelter > Tents > 1-Person Tents) that works for bikepacking but fails for sim racing gear, photography equipment, or any other hobby. Users cannot create categories, and items that span categories (a jacket that is both "clothing" and "rain gear") get awkwardly forced into one slot. The "generic enough for any hobby" goal from PROJECT.md dies on contact with a rigid hierarchy.
Why it happens: Hierarchical categories feel structured and "correct" during design. Flat tags feel messy. But hierarchies require knowing the domain upfront, and GearBox explicitly needs to support arbitrary hobbies.
How to avoid: Use a flat tag/label system as the primary organization mechanism. Users create their own tags ("bikepacking", "sleep-system", "cook-kit"). An item can have multiple tags. Optionally allow a single "category" field for broad grouping, but do not enforce hierarchy. Tags are the flexible axis; a single category field is the structured axis.
Warning signs:
- Schema has a
category_idforeign key to acategoriestable withparent_id - Seed data contains a pre-built category tree
- Adding a new hobby requires modifying the database
Phase to address: Phase 1 (Data model) -- this is a schema-level decision. Changing from hierarchy to tags after data exists requires migration of every item's categorization.
Pitfall 3: Planning Thread State Machine Complexity Explosion
What goes wrong: Thread items have statuses (researching, ordered, arrived) plus a thread-level resolution (pick winner, close thread, move to collection). Developers build these as independent fields without modeling the valid state transitions, leading to impossible states: an item marked "arrived" in a thread that was "cancelled," or a "winner" that was never "ordered." The UI then needs defensive checks everywhere, and bugs appear as ghost items in the collection.
Why it happens: Status tracking looks simple -- it is just a string field. But the combination of item-level status + thread-level lifecycle + the "move winner to collection" side effect creates a state machine with many transitions, and without explicit modeling, invalid states are reachable.
How to avoid: Model the thread lifecycle as an explicit state machine with defined transitions. Document which item statuses are valid in each thread state. The "resolve thread" action should be a single transaction that: (1) validates the winner exists, (2) creates the collection item, (3) marks the thread as resolved, (4) updates the thread item status. Use a state diagram during design, not just field definitions.
Warning signs:
- Thread status and item status are independent string/enum fields with no transition validation
- No transaction wrapping the "resolve thread + create collection item" flow
- UI shows impossible combinations (resolved thread with "researching" items)
Phase to address: Phase 2 (Planning threads) -- design the state machine before writing any thread code. Do not add statuses incrementally.
Pitfall 4: Image Storage Strategy Causes Data Loss or Bloat
What goes wrong: Two failure modes: (A) Images stored as file paths break when files are moved, deleted, or the app directory changes. Dangling references show broken image icons everywhere. (B) Images stored as BLOBs in SQLite bloat the database, slow down backups, and make the DB file unwieldy as the collection grows.
Why it happens: Image storage seems like a simple problem. File paths are the obvious approach but create a coupling between database records and filesystem state. BLOBs seem self-contained but do not scale with photo-heavy collections.
How to avoid:
Store images in a dedicated directory within the app's data folder (e.g., data/images/{item-id}/). Store relative paths in the database (never absolute). Generate deterministic filenames from item ID + timestamp to avoid collisions. On item deletion, clean up the image directory. For thumbnails under 100KB, SQLite BLOBs are actually 35% faster than filesystem reads, so consider storing thumbnails as BLOBs while keeping full-size images on disk.
Warning signs:
- Absolute file paths in the database
- No cleanup logic when items are deleted (orphaned images accumulate)
- Database file growing much larger than expected (images stored as BLOBs)
- No fallback/placeholder when an image file is missing
Phase to address: Phase 1 (Core CRUD with item photos) -- image handling must be decided before any photos are stored. Migrating image storage strategy later requires moving files and updating every record.
Pitfall 5: Setup Composition Breaks on Collection Changes
What goes wrong: A setup ("Summer Bikepacking") references items from the collection. When an item is deleted from the collection, updated, or replaced via a planning thread resolution, the setup silently breaks -- showing stale data, missing items, or incorrect totals. The user's carefully composed setup becomes untrustworthy.
Why it happens: Setups are modeled as a simple join table (setup_id, item_id) without considering what happens when the item side changes. The relationship is treated as static when it is actually dynamic.
How to avoid:
Use foreign keys with explicit ON DELETE behavior (not CASCADE -- that silently removes setup entries). When an item is deleted, mark the setup-item link as "removed" and show a visual indicator in the setup view ("1 item no longer in collection"). When a planning thread resolves and replaces an item, offer to update setups that contained the old item. Setups should always recompute totals from live item data, never cache them.
Warning signs:
- Setup totals are stored as columns rather than computed from item data
- No foreign key constraints between setups and items
- Deleting a collection item does not check if it belongs to any setup
- No UI indication when a setup references a missing item
Phase to address: Phase 3 (Setups) -- but the foreign key design must be planned in Phase 1 when the items table is created. The item schema needs to anticipate setup references.
Pitfall 6: Comparison View That Does Not Actually Help Decisions
What goes wrong: The side-by-side comparison in planning threads shows raw data (weight: 450g, price: $120) without context. Users cannot see at a glance which candidate is lighter, cheaper, or how each compares to what they already own. The comparison becomes a formatted table, not a decision tool. Users go back to their spreadsheet because it was easier to add formulas.
Why it happens: Building a comparison view that displays data is easy. Building one that surfaces insights ("this is 30% lighter than your current tent but costs 2x more") requires computing deltas against the existing collection, which is a different feature than just showing two items side by side.
How to avoid: Design comparison views to show: (1) absolute values for each candidate, (2) deltas between candidates (highlighted: lighter/heavier, cheaper/more expensive), (3) delta against the current item being replaced from the collection. Use color coding or directional indicators (green down arrow for weight savings, red up arrow for cost increase). This is the core value proposition of GearBox -- do not ship a comparison that is worse than a spreadsheet.
Warning signs:
- Comparison view is a static table with no computed differences
- No way to link a thread to "the item I'm replacing" from the collection
- Weight/cost impact on overall setup is not visible from the thread view
Phase to address: Phase 2 (Planning threads) -- comparison is the heart of the thread feature. Build the delta computation alongside the basic thread CRUD, not as a follow-up.
Technical Debt Patterns
Shortcuts that seem reasonable but create long-term problems.
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|---|---|---|---|
| Caching setup totals in a column | Faster reads, simpler queries | Stale data when items change, bugs when totals disagree with item sum | Never -- always compute from source items |
| Storing currency as float | Simple to implement | Floating point rounding errors in price totals (classic $0.01 bugs) | Never -- use integer cents or a decimal type |
| Skipping "replaced by" links in threads | Simpler thread resolution | Cannot track upgrade history, cannot auto-update setups | Only in earliest prototype, must add before thread resolution ships |
| Hardcoding unit labels | Faster initial development | Cannot support multiple hobbies with different unit conventions (e.g., ml for water bottles) | MVP only if unit conversion layer is planned for next phase |
| Single image per item | Simpler UI and storage | Gear often needs multiple angles, especially for condition tracking | Acceptable for v1 if schema supports multiple images (just limit UI to one) |
Integration Gotchas
Common mistakes when connecting to external services.
| Integration | Common Mistake | Correct Approach |
|---|---|---|
| Product link scraping | Attempting to auto-fetch product details from URLs, which breaks constantly as sites change layouts | Store the URL as a plain link. Do not scrape. Let users enter details manually. Scraping is a maintenance burden that exceeds its value for a single-user app. |
| Image URLs vs local storage | Hotlinking product images from retailer sites, which break when products are delisted | Always download and store images locally. External URLs rot within months. |
| Export/import formats | Building a custom JSON format that only GearBox understands | Support CSV import/export as the universal fallback. Users are migrating from spreadsheets -- CSV is their native format. |
Performance Traps
Patterns that work at small scale but fail as usage grows.
| Trap | Symptoms | Prevention | When It Breaks |
|---|---|---|---|
| Loading all collection items for every setup view | Slow page loads, high memory usage | Paginate collection views; setup views should query only member items | 500+ items in collection |
| Recomputing all setup totals on every item edit | Edit latency increases linearly with number of setups | Only recompute totals for setups containing the edited item | 20+ setups referencing overlapping items |
| Storing full-resolution photos without thumbnails | Page loads become unusably slow when browsing collection | Generate thumbnails on upload; use thumbnails in list views, full images only in detail view | 50+ items with photos |
| Loading all thread candidates for comparison | Irrelevant for small threads, but threads can accumulate many "considered" items | Limit comparison view to 3-4 selected candidates; archive dismissed ones | 15+ candidates in a single thread |
Security Mistakes
Domain-specific security issues beyond general web security.
| Mistake | Risk | Prevention |
|---|---|---|
| No backup mechanism for SQLite database | Single file corruption = total data loss of entire collection | Implement automatic periodic backups (copy the .db file). Provide a manual "export all" button. Single-user apps have no server-side backup by default. |
| Product URLs stored without sanitization | Stored URLs could contain javascript: protocol or XSS payloads if rendered as links | Validate URLs on save (must be http/https). Render with rel="noopener noreferrer". |
| Image uploads without size/type validation | Malicious or accidental upload of huge files or non-image files | Validate file type (accept only jpg/png/webp) and enforce max size (e.g., 5MB) on upload. |
UX Pitfalls
Common user experience mistakes in this domain.
| Pitfall | User Impact | Better Approach |
|---|---|---|
| Requiring all fields to add an item | Users abandon data entry because they do not know the weight or price yet for items they already own | Only require name. Make weight, price, category, etc. optional. Users fill in details over time. |
| No bulk operations for collection management | Adding 30 existing items one-by-one is painful enough that users never finish initial setup | Provide CSV import for initial collection population. Consider a "quick add" mode with minimal fields. |
| Thread resolution is destructive | User resolves a thread and loses all the research notes and rejected candidates | Archive resolved threads, do not delete them. Users want to reference why they chose item X over Y months later. |
| Flat item list with no visual grouping | Collection becomes an unscannable wall of text at 50+ items | Group by tag/category in the default view. Provide sort options (weight, price, date added). Show item thumbnails in list view. |
| Weight displayed without context | "450g" means nothing without knowing if that is heavy or light for this category | Show weight relative to the lightest/heaviest item in the same category, or relative to the item being replaced |
| No "undo" for destructive actions | Accidental deletion of an item with detailed notes is unrecoverable | Soft-delete with a 30-day trash, or at minimum a confirmation dialog that names the item being deleted |
"Looks Done But Isn't" Checklist
Things that appear complete but are missing critical pieces.
- Item CRUD: Often missing image cleanup on delete -- verify orphaned images are removed when items are deleted
- Planning threads: Often missing the "link to existing collection item being replaced" -- verify threads can reference what they are upgrading
- Setup composition: Often missing recomputation on item changes -- verify that editing an item's weight updates all setups containing it
- CSV import: Often missing unit detection/conversion -- verify that importing "5 oz" vs "142g" both result in correct canonical storage
- Thread resolution: Often missing setup propagation -- verify that resolving a thread and adding the winner to collection offers to update setups that contained the replaced item
- Comparison view: Often missing delta computation -- verify that the comparison shows differences between candidates, not just raw values side by side
- Dashboard totals: Often missing staleness handling -- verify dashboard stats reflect current data, not cached snapshots
- Item deletion: Often missing setup impact check -- verify the user is warned "This item is in 3 setups" before confirming deletion
Recovery Strategies
When pitfalls occur despite prevention, how to recover.
| Pitfall | Recovery Cost | Recovery Steps |
|---|---|---|
| Mixed units without conversion | MEDIUM | Add unit column to items table. Write a migration script that prompts user to confirm/correct units for existing items. Recompute all setup totals. |
| Rigid category hierarchy | HIGH | Migrate categories to tags (each leaf category becomes a tag). Update all item references. Redesign category UI to tag-based UI. |
| Thread state machine bugs | MEDIUM | Audit all threads for impossible states. Write a cleanup script. Add transition validation. Retest all state transitions. |
| Image path breakage | LOW-MEDIUM | Write a script that scans DB for broken image paths. Move images to canonical location. Update paths. Add fallback placeholder. |
| Stale setup totals | LOW | Drop cached total columns. Replace with computed queries. One-time migration, no data loss. |
| Currency as float | MEDIUM | Multiply all price values by 100, change column type to integer (cents). Rounding during conversion may lose sub-cent precision. |
Pitfall-to-Phase Mapping
How roadmap phases should address these pitfalls.
| Pitfall | Prevention Phase | Verification |
|---|---|---|
| Unit handling | Phase 1: Data model | Schema stores canonical grams + original unit. Conversion utility exists with tests. |
| Category rigidity | Phase 1: Data model | Items have a tags array/join table. No hierarchical category table exists. |
| Image storage | Phase 1: Core CRUD | Images stored in data/images/ with relative paths. Thumbnails generated on upload. Cleanup on delete. |
| Currency precision | Phase 1: Data model | Price stored as integer cents. Display layer formats to dollars/euros. |
| Thread state machine | Phase 2: Planning threads | State transitions documented in code. Invalid transitions throw errors. Resolution is transactional. |
| Comparison usefulness | Phase 2: Planning threads | Comparison view shows deltas. Thread can link to "item being replaced." Setup impact visible. |
| Setup integrity | Phase 3: Setups | Totals computed from live data. Item deletion warns about setup membership. Soft-delete or archive for removed items. |
| Data loss / no backup | Phase 1: Infrastructure | Automatic DB backup on a schedule. Manual export button on dashboard. |
| Bulk import | Phase 1: Core CRUD | CSV import available from collection view. Handles unit variations in weight column. |
Sources
- Ultralight: The Gear Tracking App I'm Leaving LighterPack For -- LighterPack limitations and community complaints
- SQLite Internal vs External BLOBs -- official SQLite guidance on image storage tradeoffs
- 35% Faster Than The Filesystem -- SQLite BLOB performance data
- Comparison Tables for Products, Services, and Features - NN/g -- comparison UX best practices
- Designing The Perfect Feature Comparison Table - Smashing Magazine -- comparison table design patterns
- Comparing products: UX design best practices - Contentsquare -- product comparison UX pitfalls
- Common Unit Conversion Mistakes That Break Applications -- unit conversion antipatterns
- Inventory App Design - UXPin -- inventory app UX patterns
- Designing better file organization around tags, not hierarchies -- tags vs hierarchy tradeoffs
Pitfalls research for: GearBox -- gear management and purchase planning app Researched: 2026-03-14