Commit graph

118 commits

Author SHA1 Message Date
d7c8a8bca6 docs(readme): landing page rewrite — corrected tagline, hero screenshot, platform table, sniping engine roadmap, split license
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-05-06 08:51:37 -07:00
108f63b4f2 fix(browser-pool): replace queue with thread-local storage to fix Playwright cross-thread crash (#53)
Playwright's sync API binds its greenlet event loop to the creating thread.
Sharing pre-warmed slots across threads caused "cannot switch to a different
thread" panics under uvicorn. New design: each worker thread owns its own
Playwright instance created lazily on first fetch_html() call. A registry
dict keyed by thread-id lets stop() close all slots at shutdown. Removes
ThreadPoolExecutor warmup and idle-cleanup daemon thread entirely.
2026-05-04 09:27:20 -07:00
bccedb1fe5 fix(trust): treat feedback_ratio=0.0 as missing data for buyer-only/returning sellers (#52)
eBay omits the 12-month positive percentage for returning sellers and
buyer-only accounts with no recent sales. Previously ratio=0.0 with
count>0 triggered established_bad_actor; now it returns None from the
scorer (score_is_partial=True) and emits a soft no_recent_seller_data
flag instead. ratio=0.0 with count=0 is still treated as no-history.
2026-05-04 09:24:27 -07:00
89d3862f62 feat(monitor): background saved-search monitoring with watch alerts (#12)
Some checks failed
CI / Python tests (push) Has been cancelled
CI / Frontend typecheck + tests (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
Backend:
- Migrations 013-015: eBay user tokens, monitor settings on saved_searches
  (monitor_enabled, poll_interval_min, min_trust_score, last_checked_at),
  watch_alerts table with UNIQUE dedup on (saved_search_id, platform_listing_id),
  active_monitors registry for cross-user polling
- WatchAlert model + store methods: upsert_alert, list_alerts, dismiss_alert,
  count_undismissed_alerts, dismiss_all_alerts, list_active_monitors
- monitor.py: run_monitor_search() using TrustScorer.score_batch(); should_alert()
  with BIN/auction/partial-score logic (auction window = 24h, partial +10 buffer)
- PATCH /api/saved-searches/{id}/monitor, GET /api/alerts, POST /api/alerts/*/dismiss
- Background polling loop at startup (asyncio.to_thread every 60s check cycle)
- ebay/adapter.py: enrich_seller_trading_api() via Trading API GetUser (OAuth token)
- nginx: raise proxy_read_timeout to 120s for slow eBay search responses

Frontend:
- AlertBell component: bell button + unread badge + panel with dismiss/clear-all;
  polls /api/alerts every 2 minutes; aria-live announcement on count change
- alerts.ts Pinia store: fetchAlerts, dismiss, dismissAll
- SavedSearchesView: monitor toggle + poll interval + min trust score controls
- SettingsView: eBay OAuth connect/disconnect section
- AppNav: AlertBell wired for logged-in and local-tier users

Tests: 24 monitor tests (should_alert branches, store alert CRUD, run_monitor_search
with mocked adapter); fix browser_pool test assertions for new wait_for_* params.
2026-05-04 08:24:56 -07:00
ac5e6166c9 docs: update roadmap — mark shipped issues, add platform expansion section
Reorganize roadmap into 5 sections: Intelligence features, Platform
expansion, Cloud/infrastructure, Auction sniping engine, Already shipped.

Mark #1, #2, #4, #6, #8, #11, #27, #29, #47, #48, #49, #50 as shipped
(all closed in Forgejo). Add #21, #43, #51, #52 (open intelligence
issues), #45 (Postgres migration), #46 (ActivityPub), #53 (BrowserPool
thread-safety). Promote Mercari to Platform expansion section.
2026-05-04 07:29:44 -07:00
8e0ec01b8f docs: update README for Mercari Phase 2 2026-05-04 07:24:16 -07:00
15996472b7 feat(mercari): Phase 2 — MercariAdapter with Xvfb stability fixes
Implements full Mercari scraping support for the trust-scoring pipeline:

- `app/platforms/mercari/` — new MercariAdapter (scraper-based), scraper
  (parse_search_html / parse_listing_html), and __init__
- `app/platforms/__init__.py` — adds "mercari" to SUPPORTED_PLATFORMS
- `api/main.py` — platform routing: _make_adapter, OR-group guard, seller
  lookup, BTF/Trading API guards all parameterised by platform
- `web/src/views/SearchView.vue` — enables Mercari tab in platform picker

BrowserPool stability fixes (browser_pool.py):
- Add -ac flag to Xvfb (disables X11 auth requirement in Docker containers)
- Shift display counter from :100-:199 to :200-:399 (avoids ghost kernel
  socket conflicts with low-numbered displays)
- Add wait_for_selector / wait_for_timeout_ms params to fetch_html,
  _fetch_with_slot, _fetch_fresh
- Add time.sleep(0.3) in _fetch_fresh after Xvfb start (was missing)

Mercari scraper fix:
- Remove sortBy=SORT_SCORE from build_search_url — that param is deprecated
  on Mercari and causes an empty 85KB response instead of search results

Probe + debug scripts in scripts/:
- probe_mercari.py — standalone Cloudflare bypass test
- debug_fetch_fresh.py — pool simulation diagnostic

Trust signal coverage: feedback_count, feedback_ratio partial score
(account_age_days, category_history absent = score_is_partial=True).
get_completed_sales stubbed for Phase 3.
Tracks: snipe#53 (pool thread-safety fix, follow-up)
2026-05-03 18:39:25 -07:00
f48f8ef80f feat: multi-platform scaffolding — phase 1 (eBay-only, wire complete)
Backend:
- app/platforms/__init__.py: add SUPPORTED_PLATFORMS frozenset (single
  source of truth for platform validation); add must_include_mode and
  adapter fields to SearchFilters dataclass
- api/main.py: add platform: str = Query("ebay") to both /api/search
  and /api/search/async; validate against SUPPORTED_PLATFORMS (422 on
  unknown platform); thread platform into structured log lines; document
  Phase 2 registry extension point in _make_adapter

Frontend:
- SearchView.vue: platform tab strip (eBay active, Mercari + Poshmark
  disabled with "soon" badge) above search bar; eBay-specific controls
  (category select, data source, pages, keywords) hidden when platform
  !== 'ebay'; platform passed to SearchProgress
- search.ts: platform?: string added to SearchFilters; included in
  async search params when non-eBay
- SearchProgress.vue: platform prop + PLATFORM_LABELS map; status line
  reads "Searching eBay for…" / "Searching Mercari for…" dynamically
2026-05-02 20:09:36 -07:00
b993f6f4a9 feat(ux): active search indicator + Candycore easter egg theme
Search indicator:
- SearchProgress.vue: indeterminate amber progress bar + status line
  + 4 staggered skeleton cards shown while loading=true and no results yet
  (fills the previously-blank results area during initial scrape phase)
- Re-search badge: blue "Re-searching…" pill in toolbar when loading=true
  over existing stale results (distinct from the amber enrichment badge)

Candycore theme:
- New [data-candycore="active"] CSS block; palette sourced from
  snipe_v0_Neon_IPad_Paint.jpeg — purple-black sky, lavender primary,
  cyan glow, yellow crown, bubblegum pink text
- useCandycoreMode.ts: word trigger ("neon", typed outside form fields),
  ascending arpeggio audio, localStorage persistence, restore on reload
- Mutually exclusive with Snipe Mode (each deactivates the other)
- Added :not([data-candycore="active"]) guards to existing dark/light
  theme override selectors so they don't stomp on Candycore
2026-05-01 23:11:36 -07:00
05f845962f fix(trust): soften established_bad_actor for high-volume sellers; add declining_ratio flag
Some checks failed
CI / Python tests (push) Has been cancelled
CI / Frontend typecheck + tests (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Release / release (push) Has been cancelled
Fixes a false-positive edge case (snipe#52) where sellers with 500+
lifetime feedback were hard-flagged as established_bad_actor when the
12-month ratio dipped below 80% — even though the 12-month window may
cover only a small recent sample relative to lifetime history.

Changes:
- established_bad_actor hard filter now only fires for accounts with
  20–500 lifetime feedback (unchanged behavior for moderate accounts)
- Accounts >500 feedback with ratio 60–80%: new declining_ratio soft flag
  (composite score penalised but not zeroed, no hard block)
- Accounts >500 feedback with ratio <60%: still established_bad_actor
  (catastrophically bad even for high-volume sellers)
- Two new constants: HARD_FILTER_BAD_RATIO_MAX_COUNT=500,
  HARD_FILTER_BAD_RATIO_HIGH_THRESHOLD=0.60

Note: buyer-feedback-only accounts (lifetime buyer history inflating
feedback_count for new sellers) requires profile-page scraping to detect
properly — tracked in snipe#52 as medium-term work.

Tests: 22 passed
2026-04-27 12:54:51 -07:00
0354234f86 refactor: replace hand-rolled JWT+Heimdall with cf-core CloudSessionFactory
Some checks failed
CI / Python tests (push) Has been cancelled
CI / Frontend typecheck + tests (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Delegates JWT validation, Heimdall provision/tier-resolve, bypass-IP
handling, and guest session management to circuitforge_core. Snipe keeps
its own CloudUser (shared_db + user_db), SessionFeatures, compute_features,
and DB path helpers. Removes ~158 lines of duplicated auth code.

Note: get_session() now takes (Request, Response) — FastAPI auto-injects
both, no call-site changes needed.
2026-04-25 16:35:41 -07:00
ec0af07905 feat: add CF_APP_NAME=snipe to cloud compose for cf-orch pipeline attribution
Some checks failed
CI / Python tests (push) Has been cancelled
CI / Frontend typecheck + tests (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
2026-04-21 10:58:52 -07:00
7abc765fe7 Merge branch 'feature/perf-pool-cache'
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-20 12:10:16 -07:00
0ec29f0551 feat(scraper): pre-warmed Chromium browser pool (BROWSER_POOL_SIZE=2 default) 2026-04-20 12:09:09 -07:00
29d2033ef2 feat: browser pool + search result cache (#47, #48)
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-20 11:57:56 -07:00
a83e0957e2 feat(api): short-TTL search result cache (SEARCH_CACHE_TTL_S=300 default) 2026-04-20 11:53:27 -07:00
844721c6fd feat: near-term UX batch -- URL normalization, currency preference, async search/SSE
Some checks failed
CI / Python tests (push) Waiting to run
CI / Frontend typecheck + tests (push) Waiting to run
Release / release (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
2026-04-20 11:04:52 -07:00
dca3c3f50b feat(prefs): display.currency preference with live exchange rate conversion
- Backend: validate display.currency against 10 supported ISO 4217 codes
  (USD, GBP, EUR, CAD, AUD, JPY, CHF, MXN, BRL, INR); return 400 on
  unsupported code with a clear message listing accepted values
- Frontend: useCurrency composable fetches rates from open.er-api.com
  with 1-hour module-level cache and in-flight deduplication; falls back
  to USD display on network failure
- Preferences store: adds display.currency with localStorage fallback for
  anonymous users and localStorage-to-DB migration for newly logged-in users
- ListingCard: price and market price now convert from USD using live rates,
  showing USD synchronously while rates load then updating reactively
- Settings UI: currency selector dropdown in Appearance section using
  theme-aware CSS classes; available to all users (anon via localStorage,
  logged-in via DB preference)
- Tests: 6 Python tests for the PATCH /api/preferences currency endpoint
  (including ordering-safe fixture using patch.object on _LOCAL_SNIPE_DB);
  14 Vitest tests for convertFromUSD, formatPrice, and formatPriceUSD
2026-04-20 11:02:59 -07:00
d5912080fb feat(search): async endpoint + SSE streaming for initial results
Add GET /api/search/async that returns HTTP 202 immediately and streams
scrape results via SSE to avoid nginx 120s timeouts on slow eBay searches.

Backend:
- New GET /api/search/async endpoint submits scraping to ThreadPoolExecutor
  and returns {session_id, status: "queued"} before scrape begins
- Background worker runs same pipeline as synchronous search, pushing
  typed SSE events: "listings" (initial batch), "update" (enrichment),
  "market_price", and None sentinel
- Existing GET /api/updates/{session_id} passes new event types through
  as-is (already a generic pass-through); deadline extended to 150s
- Module-level _search_executor (max_workers=4) caps concurrent scrape sessions

Frontend (search.ts):
- search() now calls /api/search/async instead of /api/search
- loading stays true until first "listings" SSE event arrives
- _openUpdates() handles new typed events: "listings", "market_price",
  "update"; legacy untyped enrichment events still handled
- cancelSearch() now also closes any open SSE stream

Tests: tests/test_async_search.py (6 tests) covering 202 response,
session_id registration in _update_queues, empty query path, UUID format,
and no-Chromium guarantee. All 159 pre-existing tests still pass.

Closes #49. Also closes Forgejo issue #1 (SSE enrichment streaming, already
implemented; async search completes the picture).
2026-04-20 10:57:32 -07:00
2e0a49bc12 docs(config): add cf_text trunk service backend to llm.yaml.example
Documents the cf-orch allocation pattern (cf_text openai_compat backend
with cf_orch block). Snipe's trust query builder can route through
cf-text when CF_ORCH_URL is set, rather than hitting ollama directly.
2026-04-20 10:56:23 -07:00
df4610c57b feat(search): normalize eBay listing + checkout URLs as item lookup
When the user pastes an eBay listing URL (www.ebay.com/itm/...) or an
eBay checkout URL (pay.ebay.com/rxo?itemId=...) into the search field,
extract the numeric item ID and use it as the search query.

Supported URL patterns:
- https://www.ebay.com/itm/Title-Slug/123456789012
- https://www.ebay.com/itm/123456789012
- https://ebay.com/itm/123456789012
- https://pay.ebay.com/rxo?action=view&sessionid=...&itemId=123456789012
- https://pay.ebay.com/rxo/view?itemId=123456789012

Closes #42
2026-04-20 10:49:17 -07:00
349cff8c50 chore: ignore .worktrees/ directory 2026-04-20 10:45:39 -07:00
90f72d6e53 feat(config): add CF_APP_NAME for cf-orch analytics attribution 2026-04-20 07:03:18 -07:00
e539427bec fix: catch sqlite3.OperationalError in search post-processing
Under high concurrency (100+ users), shared_db write contention causes
database is locked errors in the unguarded post-scrape block. These were
surfacing as 500s because there was no exception handler after line 663.

Now catches OperationalError and returns raw listings with empty trust
scores/sellers (degraded mode) instead of crashing. The SSE queue entry
is cleaned up on this path so no orphaned queue accumulates.

Root cause: shared_db (sellers, market_comps) is SQLite; at 100 concurrent
writers the WAL write queue exceeds the 30s busy timeout. Long-term fix
is migrating shared state to Postgres (see snipe#NN).

Refs: infra#12 load test Phase 2 spike findings
2026-04-19 21:26:20 -07:00
ed6d509a26 fix: authenticate eBay public key fetch + add webhook health endpoint
Fixes recurring `400 Missing access token` errors in Snipe logs.
`_fetch_public_key()` was making unauthenticated GET requests to
eBay's Notification API (`/commerce/notification/v1/public_key/{kid}`),
which requires an app-level Bearer token (client_credentials grant).

Wires in the existing `EbayTokenManager` as a lazy module-level
singleton so every public key fetch carries a valid OAuth token.

Also adds `GET /api/ebay/webhook-health` for Uptime Kuma compliance
monitoring — returns 200 + status dict when all five required env vars
are present, 500 with missing var names otherwise.

Runbook: circuitforge-plans/snipe/ebay-webhook-compliance-runbook.md
Kuma monitor: id=19 on heimdall status page (Snipe group)
2026-04-18 22:20:29 -07:00
16cd32b0db fix: body background follows theme tokens + Plausible analytics
- theme.css: add background: var(--color-surface) to body so it responds
  to theme changes (was hardcoded #0d1117 via FOFT guard in index.html,
  causing mixed dark/light on light theme)
- index.html: add Plausible analytics snippet (cookie-free, self-hosted,
  skips localhost; reports to hostname + circuitforge.tech rollup)
- index.html: clarify FOFT guard comment — bundle overrides both html
  and body once loaded
2026-04-17 03:00:16 -07:00
dbe9aaa00b feat: add Plausible analytics to Vue SPA and docs
Some checks failed
CI / Python tests (push) Has been cancelled
CI / Frontend typecheck + tests (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
2026-04-16 21:15:56 -07:00
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