Commit graph

91 commits

Author SHA1 Message Date
9c2a2d6bf2 chore: bump changelog for v0.5.1
Some checks failed
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
2026-04-16 13:37:03 -07:00
66ae9eb0b8 feat: reported sellers tracking + community blocklist opt-in
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
Phase 2 (snipe#4): after bulk-reporting sellers to eBay T&S, Snipe now
persists which sellers were reported so cards show a muted "Reported to
eBay" badge and users aren't prompted to re-report the same seller.
- migration 012 adds reported_sellers table (user DB, UNIQUE on seller)
- Store.mark_reported / list_reported methods
- POST /api/reported + GET /api/reported endpoints
- reported store (frontend) with optimistic update + server persistence
- reportSelected wires into store after opening eBay tabs

Phase 3 prep (snipe#4): community blocklist share toggle
- Settings > Community section: "Share blocklist with community" toggle
  (visible only to signed-in cloud users, default OFF)
- Persisted as community.blocklist_share user preference
- Backend community signal publish now gated on opt-in preference;
  privacy-by-architecture: sharing is explicit, never implicit
2026-04-16 13:28:57 -07:00
7005be02c2 test: aggregator coverage for zero_feedback, long_on_market, significant_price_drop
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
Add 10 new tests covering the three previously untested flag paths:
- zero_feedback: flag fires + composite capped at 35 even with all-20 signals
- long_on_market: fires at >=5 sightings + >=14 days; NOT at <5 sightings or <14 days
- significant_price_drop: fires at >=20% below first-seen; NOT at <20% or no prior price
- established_retailer: duplicate_photo suppressed at feedback>=1000; fires below threshold

Also fix datetime.utcnow() deprecation in aggregator._days_since() and test helper
— replaced with timezone-aware datetime.now(timezone.utc) throughout.
2026-04-16 13:14:20 -07:00
873b9a1413 feat: structured auth logging + log analytics foundation
Some checks failed
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
Add _auth_label() classifier: local/anon/guest/authed — no PII, just
enough to distinguish traffic types in docker logs for log-based analytics.

Instrument /api/session: logs new_guest (with UUID) or auth=.../tier=...
on every session bootstrap. Instrument /api/search: expands existing
multi-search log line with auth=, tier=, adapter=, pages=, queries=,
listings= fields for grep/awk analysis of search behaviour by tier.

Add logging.basicConfig so app-level log.info() calls appear in docker
logs alongside the Uvicorn access log (previously suppressed by missing
root handler).
2026-04-16 13:04:46 -07:00
8e40e51ed3 fix: prefix /api/session and /api/preferences with VITE_API_BASE
Some checks are pending
CI / Frontend typecheck + tests (push) Waiting to run
CI / Python tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
Release / release (push) Waiting to run
Both stores hardcoded /api/* paths without reading VITE_API_BASE, causing
/api/session to hit the menagerie root (no /snipe prefix) and receive a
302 redirect to circuitforge.tech/login instead of a session response.
Every other store already read VITE_API_BASE correctly.
2026-04-16 12:51:39 -07:00
e428c1446e docs: release v0.5.0 changelog
Some checks failed
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
2026-04-16 12:43:55 -07:00
af51de4cec feat: landing hero copy polish + frontend test suite
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
- Rewrite landing hero subtitle with narrative opener ("Seen a listing
  that looks almost too good to pass up?") — more universal than the
  feature-list version, avoids category assumptions
- Update eBay cancellation callout CTA to "Search above to score
  listings before you commit" — direct action vs. passive reminder
- Tile descriptions rewritten with concrete examples: "does this seller
  actually know what they're selling?", "40% below median", quoted
  "scratch and dent" pattern for instant recognition
- Sign-in strip: add specific page count ("up to 5 pages") and
  "community-maintained" attribution for blocklist credibility
- Fix useTheme restore() to re-read from localStorage instead of using
  cached module-level ref — fixes test isolation failure where previous
  test's setMode('dark') leaked into the restore() system test
- Add 32-test Vitest suite: useTheme (7), searchStore (7),
  ListingView component (18) — all green
2026-04-16 12:39:54 -07:00
9734c50c19 feat: explicit dark/light theme override with Settings toggle
Adds user-controlled theme selection independent of OS preference:

- useTheme composable: sets data-theme="dark"|"light" on <html>,
  persisted to localStorage as snipe:theme. Follows the same pattern
  as useSnipeMode.
- theme.css: [data-theme="dark"] and [data-theme="light"] explicit
  attribute selectors override @media (prefers-color-scheme: light).
  Media query updated to :root:not([data-theme="dark"]) so it has no
  effect when the user has forced dark on a light-OS machine.
- App.vue: restoreTheme() called in onMounted alongside restoreSnipeMode.
- SettingsView: Appearance section with System/Dark/Light segmented
  button group.
2026-04-16 11:52:10 -07:00
c90061733c feat: listing detail page with trust score ring, signal breakdown, seller panel
Replaces the Coming Soon placeholder. Clicking Details on any card opens
a full trust breakdown view:

- SVG score ring with composite score and colour-coded verdict label
- Auto-generated verdict text (identifies worst signals in plain English)
- Signal table with mini-bars: Feedback Volume/Ratio, Account Age,
  Price vs Market, Category History — pending state shown for unresolved
- Red flag badges (hard vs soft) above the score ring
- Photo carousel with prev/next controls and img-error skip
- Seller panel (feedback count/ratio, account age, pending enrichment note)
- Block seller inline form wired to POST /api/blocklist
- Triple Red pulsing border easter egg carried over from card
- Not-found state for direct URL access (store cleared on refresh)
- Responsive: single-column layout on ≤640px
- ListingCard: adds Details RouterLink to price column
- search store: adds getListing(id) lookup helper
2026-04-16 11:48:30 -07:00
29922ede47 ci: register browser pytest marker
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
Silences PytestUnknownMarkWarning from '-m not browser' in GitHub
Actions CI. Any test requiring headed Chromium (Kasada bypass,
scraper integration) should be decorated with @pytest.mark.browser
so it is excluded from CI automatically.

Closes #40
2026-04-15 20:30:26 -07:00
f53aae1ae0 ci: add GitHub Actions CI for public credibility badge
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
Lean self-contained workflow — no Forgejo-specific secrets.
circuitforge-core installs from Forgejo git (public repo).
Forgejo (.forgejo/workflows/ci.yml) remains the canonical CI.

Backend: ruff + pytest (browser tests skipped) | Frontend: vue-tsc + vitest
2026-04-15 20:20:13 -07:00
8b6f4b5b90 docs: release v0.4.0 changelog
Some checks failed
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
2026-04-14 16:18:59 -07:00
f8dd1d261d feat: preferences store, community signals, a11y + API fixes
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
- Store: add save_community_signal, get/set_user_preference, get_all_preferences
- App.vue: move skip link before nav (correct a11y order); bootstrap preferences store after session
- useTrustFeedback: prefix fetch URL with VITE_API_BASE (fixes menagerie routing)
- search.ts: guard v-model.number empty-string from cleared price inputs
- Python: isort import ordering pass across adapters, trust, tasks, tests
2026-04-14 16:15:09 -07:00
af1ffa1d94 feat: wire Search with AI to cf-orch → Ollama (llama3.1:8b)
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
- Add app/llm/router.py shim — tri-level config lookup:
  repo config/llm.yaml → ~/.config/circuitforge/llm.yaml → env vars
- Add config/llm.cloud.yaml — ollama via cf-orch, llama3.1:8b
- Add config/llm.yaml.example — self-hosted reference config
- compose.cloud.yml: mount llm.cloud.yaml, set CF_ORCH_URL,
  add host.docker.internal:host-gateway (required on Linux Docker)
- api/main.py: use app.llm.router.LLMRouter (shim) not core directly
- .env.example: update LLM section to reference config/llm.yaml.example
- .gitignore: exclude config/llm.yaml (keep example + cloud yaml)

End-to-end tested: 3.2s for "used RTX 3080 under $400, no mining cards"
via cloud container → host.docker.internal:11434 → Ollama llama3.1:8b
2026-04-14 13:23:44 -07:00
c0a92315d9 fix: rename Build with AI → Search with AI, sync panel CSS to amber theme
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
- All copy: "Build with AI" → "Search with AI", "Build Search" → "Search",
  "Building…" → "Searching…", "Filters updated" → "Filters ready"
- Remove ✦ sparkle from toggle button
- Replace all var(--color-accent) (purple) with var(--app-primary) (amber)
- Fix var(--color-surface-1) → var(--color-surface) (surface-1 does not exist)
- Toggle muted at rest, amber on hover/open — matches sidebar toolbar style
- SettingsView aria-label updated to match
2026-04-14 12:32:33 -07:00
55b278ff8e fix: replace @/ path aliases with relative imports (no alias configured in vite.config)
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
2026-04-14 12:18:43 -07:00
b5779224b1 fix: sync store.filters and store.query into SearchView local state after LLM populate
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
After populateFromLLM() runs, the sidebar filter controls and search bar now
update to reflect the LLM-generated values. Adds two one-way watchers
(store.filters → local reactive, store.query → queryInput). Closes #39.
2026-04-14 12:08:18 -07:00
0919ebc76a feat: LLM query builder — natural language → eBay search filters (snipe#29)
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
2026-04-14 12:01:31 -07:00
37e34ac820 feat: mount LLMQueryPanel in SearchView and add auto-run setting to SettingsView 2026-04-14 11:50:57 -07:00
53ede9a4c5 feat: LLMQueryPanel collapsible panel with a11y wiring and theme-aware styles 2026-04-14 11:50:22 -07:00
b143962ef6 feat: useLLMQueryBuilder composable with buildQuery, autoRun, and status tracking 2026-04-14 11:49:48 -07:00
65ddd2f4fa feat: add llm_query_builder to SessionFeatures and populateFromLLM to search store 2026-04-14 11:49:22 -07:00
cdc4e40775 feat: POST /api/search/build endpoint with tier gate and category cache wiring 2026-04-14 11:46:15 -07:00
93f989c821 feat: add llm_query_builder tier gate (paid+) to tiers.py and SessionFeatures 2026-04-14 11:44:53 -07:00
7720f1def5 feat: QueryTranslator with domain-aware system prompt and category hint injection 2026-04-14 11:43:19 -07:00
3c54a65dda feat: SearchParamsResponse dataclass and JSON parser for LLM query builder 2026-04-14 11:40:44 -07:00
15718ab431 feat: community category federation in EbayCategoryCache.refresh()
Adds optional community_store param to refresh(). Credentialed instances
publish leaf categories to the shared community PostgreSQL after a
successful Taxonomy API fetch. Credentialless instances pull from community
(requires >= 10 rows) before falling back to the hardcoded bootstrap.

Adds 3 new tests (14 total, all passing).
2026-04-14 11:38:12 -07:00
0b8cb63968 feat: EbayCategoryCache refresh from eBay Taxonomy API with bootstrap fallback 2026-04-14 11:14:52 -07:00
7c73186394 feat: EbayCategoryCache get_relevant and get_all_for_prompt 2026-04-14 11:06:13 -07:00
099943b50b feat: EbayCategoryCache init, is_stale, bootstrap seed 2026-04-14 10:49:17 -07:00
979ddd8cce docs: remove Ultra tier, switch to whole-dollar pricing (/0/mo)
Some checks are pending
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
2026-04-14 10:42:46 -07:00
254fc482db feat: add ebay_categories migration for LLM query builder category cache 2026-04-14 10:40:20 -07:00
3c531f86c4 docs(env): add COMMUNITY_DB_URL to .env.example with guidance 2026-04-14 08:36:58 -07:00
3a4b33d5dd feat: wire community module + corrections router (#31 #32 #33)
Corrections (#31):
- Add 010_corrections.sql migration (from cf-core CORRECTIONS_MIGRATION_SQL)
- Wire make_corrections_router() at /api/corrections (shared_db, product='snipe')
- get_shared_db() dependency aggregates corrections across all cloud users

Community module (#32 #33):
- Init SnipeCommunityStore at startup when COMMUNITY_DB_URL is set
- Graceful skip if COMMUNITY_DB_URL is unset (local mode, community disabled)
- add_to_blocklist() publishes confirmed_scam=True seller_trust signal to
  community postgres on every manual blocklist addition (fire-and-forget)
- BlocklistAdd gains flags[] field so active red-flag keys travel with signal

cf-orch community postgres (cf-orch#36) + cf-core module (cf-core#47) both merged.
2026-04-14 08:33:00 -07:00
72b86834d8 docs: add link to docs.circuitforge.tech/snipe in README
Some checks are pending
CI / Frontend typecheck + tests (push) Waiting to run
CI / Python tests (push) Waiting to run
Mirror / mirror (push) Waiting to run
2026-04-14 08:06:11 -07:00
2dda26a911 feat: infra/devops batch — CI/CD, installer, nginx docs, cf-orch agent (v0.3.0)
Some checks failed
CI / Frontend typecheck + tests (push) Waiting to run
CI / Python tests (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
Closes #15, #22, #24, #25. Closes #1 and #27 (already shipped in 0.2.0).

## CI/CD (#22)
- .forgejo/workflows/ci.yml — Python lint (ruff) + pytest + Vue typecheck + vitest
  on every PR/push. Installs cf-core from GitHub mirror for the CI runner.
- .forgejo/workflows/release.yml — Docker build/push (api + web) to Forgejo registry
  on v* tags; git-cliff changelog; multi-arch amd64+arm64.
- .forgejo/workflows/mirror.yml — push to GitHub + Codeberg mirrors.

## Self-hosted installer (#25)
- install.sh rewritten to match CF installer pattern: coloured output, named
  functions, --docker / --bare-metal / --help flags, auto-detect Docker/conda/
  Python/Node/Chromium/Xvfb, license key prompting with format validation.

## Nginx docs (#24)
- docs/nginx-self-hosted.conf — sample nginx config: SPA fallback, SSE proxy
  (proxy_buffering off), long-term asset cache headers.
- docs/getting-started/installation.md — bare-metal install section with nginx
  setup, Chromium/Xvfb note, serve-ui.sh vs nginx trade-off.

## cf-orch agent (#15)
- compose.override.yml — cf-orch-agent sidecar service (profiles: [orch]).
  Starts only with docker compose --profile orch. Registers with coordinator at
  CF_ORCH_COORDINATOR_URL (default 10.1.10.71:7700).
- .env.example — CF_ORCH_URL / CF_ORCH_COORDINATOR_URL comments expanded.

## Docs
- mkdocs.yml + full docs/ tree (getting-started, reference, user-guide) staged
  from prior session work.

Bump version 0.2.0 → 0.3.0.
2026-04-14 06:19:25 -07:00
6d5ceac0a1 docs(screenshots): retake all screenshots post css color-mix fix
Retake 01-hero, 02-results, 03-steal-badge, and hero.png after the
color-mix() refactor (d5651e5) so docs reflect corrected light/dark
tints on STEAL badge, flags, and trust score indicators.
2026-04-13 22:48:36 -07:00
d5651e5fe8 fix(css): replace hardcoded rgba colors with color-mix(var(--token)) for light/dark parity
All error-red and success-green rgba values were using dark-mode hex values directly.
In light mode those tokens shift (error #f85149→#dc2626, success #3fb950→#16a34a),
so the hardcoded tints stayed wrong. Replaced with color-mix() so tints follow the token.

Also:
- Add missing --space-5 (1.25rem) to spacing scale in theme.css
- Add --color-accent (purple) token for csv_import badge; adapts dark/light
- Wire blocklist source badges to use --color-info/accent/success tokens
2026-04-13 20:56:07 -07:00
c93466c037 feat(mcp): Snipe MCP server for Claude Code integration (#27)
Three tools: snipe_search (GPU-scored trust-ranked), snipe_enrich (deep BTF scraping),
snipe_save (persist search to Snipe UI). GPU inference scoring uses VRAM + arch tier
weighted composite. LLM-condensed output trims verbose listing dicts to trust/price/GPU/url.

Configure via ~/.claude.json with SNIPE_API_URL env var pointing at local or cloud API.
2026-04-13 19:33:47 -07:00
fb81422c54 feat: snipe beta backlog batch (tickets #22/#28/#30/#34/#35/#36/#37/#38)
Cloud/session:
- fix(_extract_session_token): return "" for non-JWT cookie strings (snipe_guest=uuid was
  triggering 401 → forced login redirect for all unauthenticated cloud visitors)
- fix(affiliate): exclude guest: and anonymous users from pref-store writes (#38)
- fix(market-comp): use enriched comp_query for market comp hash so write/read keys match (#30)

Frontend:
- feat(SearchView): unauthenticated landing strip with free-account CTA (#36)
- feat(SearchView): aria-pressed on filter toggles, aria-label on icon buttons, focus-visible
  rings on all interactive controls, live region for result count (#35)
- feat(SearchView): no-results empty-state hint text (#36)
- feat(SEO): og:image 1200x630, summary_large_image twitter card, canonical link (#37)
- feat(OG): generated og-image.png (dark tactical theme, feature pills) (#37)
- feat(settings): TrustSignalPref view wired to /settings route (#28)
- fix(router): /settings route added; unauthenticated access redirects to home (#34)

CI/CD:
- feat(ci): Forgejo Actions workflow (ruff + pytest + vue-tsc + vitest) (#22)
- feat(ci): mirror workflow (GitHub + Codeberg on push to main/tags) (#22)
- feat(ci): release workflow (Docker build+push + git-cliff changelog) (#22)
- chore: git-cliff config (.cliff.toml) for conventional commit changelog (#22)
- chore(pyproject): dev extras (pytest/ruff/httpx), ruff config with ignore list (#22)

Lint:
- fix: remove 11 unused imports across api/, app/, tests/ (ruff F401 clean)
2026-04-13 19:32:50 -07:00
aff5bdda39 chore: bump version to 0.2.0, add CHANGELOG 2026-04-13 05:06:06 -07:00
5006a03603 feat(community): TrustFeedbackButtons + useTrustFeedback -- trust signal UI on ListingCard [MIT]
Files: web/src/composables/useTrustFeedback.ts, web/src/components/TrustFeedbackButtons.vue, web/src/components/ListingCard.vue

- useTrustFeedback composable: FeedbackState machine (idle/sending/confirmed/disputed), fail-soft fetch, always confirms regardless of network outcome
- TrustFeedbackButtons.vue: "This score looks right / This score is wrong" button pair with calm "Thanks, noted." confirmation; uses --trust-high/--trust-low theme CSS vars; aria-live="polite", aria-busy, focus-visible, prefers-reduced-motion, no countdown timers
- ListingCard.vue: TrustFeedbackButtons slotted after trust badge inside .card__score-col
2026-04-12 22:09:33 -07:00
303b4bfb6f feat: SSE live score push for background enrichment (#1)
After a search, the API now returns a session_id. If any trust scores are
partial (pending seller age or category data), the frontend opens a
Server-Sent Events stream to /api/updates/{session_id}. As the background
BTF (account age) and category enrichment threads complete, they re-score
affected listings and push updated TrustScore payloads over SSE. The
frontend patches the trustScores and sellers maps reactively so signal
dots light up without requiring a manual re-search.

Backend:
- _update_queues registry maps session_id -> SimpleQueue (thread-safe bridge)
- _trigger_scraper_enrichment accepts session_id/user_db/query, builds a
  seller->listings map, calls _push_updates() after each enrichment pass
  which re-scores, saves trust scores, and puts events on the queue
- New GET /api/updates/{session_id} SSE endpoint: polls queue every 500ms,
  emits heartbeats every 15s, closes on sentinel None or 90s timeout
- search endpoint generates session_id and returns it in response

Frontend:
- search store adds enriching state and _openUpdates() / closeUpdates()
- On search completion, if partial scores exist, opens EventSource stream
- onmessage: patches trustScores and sellers maps (new Map() to trigger
  Vue reactivity), updates marketPrice if included
- on 'done' event or error: closes stream, enriching = false
- SearchView: pulsing 'Updating scores...' badge in toolbar while enriching
2026-04-05 23:12:27 -07:00
45c758bb53 revert: remove ADD COLUMN IF NOT EXISTS (not a SQLite feature)
SQLite does not support ADD COLUMN IF NOT EXISTS regardless of version.
The idempotency fix lives in cf-core's migration runner instead.
2026-04-05 22:24:06 -07:00
59f728cba0 fix: make ALTER TABLE migrations idempotent with IF NOT EXISTS
SQLite's executescript() auto-commits each DDL statement, so a partial
migration failure leaves columns in the DB without marking the migration
done. On the next startup the runner retries and hits duplicate column errors.

Use ADD COLUMN IF NOT EXISTS (SQLite 3.35+, shipped in Python 3.11+)
so migrations 004 and 005 are safe to re-run in any partial state.
2026-04-05 22:18:25 -07:00
81e41e39ab fix: remove duplicate first_seen_at ALTER TABLE in migration 004
001_init.sql already defines first_seen_at in the CREATE TABLE statement.
On fresh installs, migration 004 failed with 'duplicate column name: first_seen_at'.
Remove the redundant ALTER TABLE; last_seen_at/times_seen/price_at_first_seen
are still added by 004 as before.
2026-04-05 22:12:47 -07:00
234c76e686 feat: add no-Docker install path (conda/venv + uvicorn + npm build) 2026-04-05 22:09:24 -07:00
7672dd758a fix: self-hosted install — network_mode, cf-core bind mount, install script 2026-04-05 22:02:50 -07:00
663d92fc11 refactor: use shorter circuitforge_core.api import for feedback router 2026-04-05 21:21:54 -07:00
c2fa107c47 fix: use correct tab field name in feedback test 2026-04-05 20:50:13 -07:00