wrongmove/frontend
Viktor Barzin c2e08fe46e wrongmove: add "seen" soft-hide decision with price-aware resurfacing
Opening a listing's detail sheet for >3s now passively marks it `seen`
and snapshots its current `total_price`. Seen listings are hidden from
the main list by default but automatically resurface when the price
changes (any direction). Distinct from `disliked` — explicit
like/dislike always overrides the passive seen state.

New "Hidden (N)" toggle on the FilterBar appears whenever at least one
listing is currently being hidden by the seen filter. Toggling it
reveals those rows in-place (without unmarking them) so the user can
review or explicitly clear via the existing Like/Dislike/Clear flow.

## Backend
- Alembic f7a8b9c0d1e2: `ALTER TABLE listingdecision ADD COLUMN
  price_at_decision FLOAT NULL`.
- `models/decision.py`: ListingDecision gains nullable
  `price_at_decision: float | None`.
- `services/decision_service.py`: adds `seen` to VALID_DECISIONS;
  set_decision accepts an optional `price_at_decision`; it's only
  forwarded to the repo for decision='seen' (other types null-out the
  column to keep semantics clean).
- `repositories/decision_repository.py`: upsert_decision now carries
  price_at_decision through the MySQL + SQLite upsert paths.
- `api/decision_routes.py`: SetDecisionRequest + DecisionResponse
  expose the new field.

## Frontend
- `types/index.ts`: DecisionType = 'liked' | 'disliked' | 'seen';
  ListingDecision gains `price_at_decision?: number | null`.
- `services/decisionService.ts`: setDecision sends the price only for
  decision='seen' (and only when it's a finite number).
- `hooks/useDecisions.ts`: rewritten to store `Map<key, DecisionEntry>`
  (decision + price snapshot). New `markSeen(id, price, type)` short-
  circuits on existing liked/disliked. New `getDecisionEntry`,
  `seenCount`.
- `App.tsx`: 3s `setTimeout` dwell timer fires markSeen when the
  detail sheet stays open. Filter logic in `processedListingData`
  hides `seen` rows whose `total_price === price_at_decision`, with
  `showHidden` bypass. Computes `hiddenCount` to drive the toggle.
- `components/FilterBar.tsx`: new conditional "Hidden (N)" / "Showing
  hidden (N)" toggle button (Eye / EyeOff lucide icons), surfaces only
  when hiddenCount > 0.

## Tests
- pytest: 2 new (test_set_seen_carries_price, test_liked_drops_price_
  even_if_supplied) + 1 updated to assert the new 5-arg repo
  signature. 24 passed.
- vitest: 6 new for useDecisions (markSeen liked/disliked skip, price
  snapshot, re-mark, null price, seenCount) + 5 new for decisionService
  payload shape. 221 total passed, tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 11:07:44 +00:00
..
public Move hexgrid heatmap computation to Web Worker 2026-02-22 15:04:37 +00:00
src wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
.dockerignore Expand frontend .dockerignore to exclude build artifacts 2026-02-21 19:51:04 +00:00
.env.sample Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
.gitignore wrongmove: write VITE_MAPBOX_TOKEN to .env.production in CI (replaces broken build_args) 2026-05-15 22:10:25 +00:00
Caddyfile.dev Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
components.json Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
Dockerfile wrongmove: write VITE_MAPBOX_TOKEN to .env.production in CI (replaces broken build_args) 2026-05-15 22:10:25 +00:00
eslint.config.js Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
index.html Move hexgrid heatmap computation to Web Worker 2026-02-22 15:04:37 +00:00
nginx.conf Fix stale browser cache after redeployment with proper nginx cache headers 2026-02-08 22:51:11 +00:00
package-lock.json chore: add react-router-dom dependency for URL-based navigation 2026-02-28 15:58:39 +00:00
package.json chore: add react-router-dom dependency for URL-based navigation 2026-02-28 15:58:39 +00:00
README.md Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
start.sh Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
tsconfig.app.json Move hexgrid heatmap computation to Web Worker 2026-02-22 15:04:37 +00:00
tsconfig.app.tsbuildinfo wrongmove: guard property cards against null backend fields (fix BUY crash) 2026-05-10 21:17:41 +00:00
tsconfig.json Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
tsconfig.node.json Remove JS obfuscator that broke Mapbox GL map rendering 2026-02-08 22:47:01 +00:00
tsconfig.node.tsbuildinfo Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
vite.config.ts Remove JS obfuscator that broke Mapbox GL map rendering 2026-02-08 22:47:01 +00:00
vitest.config.ts Fix frontend CI pipeline OOM kills and test timeouts 2026-02-17 21:46:29 +00:00

React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

export default tseslint.config({
  extends: [
    // Remove ...tseslint.configs.recommended and replace with this
    ...tseslint.configs.recommendedTypeChecked,
    // Alternatively, use this for stricter rules
    ...tseslint.configs.strictTypeChecked,
    // Optionally, add this for stylistic rules
    ...tseslint.configs.stylisticTypeChecked,
  ],
  languageOptions: {
    // other options...
    parserOptions: {
      project: ['./tsconfig.node.json', './tsconfig.app.json'],
      tsconfigRootDir: import.meta.dirname,
    },
  },
})

You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:

// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config({
  plugins: {
    // Add the react-x and react-dom plugins
    'react-x': reactX,
    'react-dom': reactDom,
  },
  rules: {
    // other rules...
    // Enable its recommended typescript rules
    ...reactX.configs['recommended-typescript'].rules,
    ...reactDom.configs.recommended.rules,
  },
})