Compare commits
No commits in common. "aff5bdda39658d7065559ee849d31521fa99e2b1" and "303b4bfb6f8a5b814a113e8990c91813adf5db70" have entirely different histories.
aff5bdda39
...
303b4bfb6f
5 changed files with 1 additions and 174 deletions
47
CHANGELOG.md
47
CHANGELOG.md
|
|
@ -1,47 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to `snipe` are documented here.
|
|
||||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
||||||
Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.2.0] — 2026-04-12
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
**Trust signal UI** — community feedback on seller trust scores (MIT component layer)
|
|
||||||
|
|
||||||
- `web/src/components/TrustFeedbackButtons.vue`: "This score looks right / This score is wrong" button pair displayed below the trust badge on each listing card. Shows "Thanks, noted." on submission with no countdown or urgency.
|
|
||||||
- `web/src/composables/useTrustFeedback.ts`: `FeedbackState` machine (`idle | sending | confirmed | disputed`). Fail-soft: any network error still transitions to confirmed state — the UI never surfaces signal pipeline failures.
|
|
||||||
- Slotted into `ListingCard.vue` after the trust badge, inside `.card__score-col`.
|
|
||||||
- WCAG (Web Content Accessibility Guidelines) 2.1 compliance: `aria-live="polite"` on confirmation message, `aria-busy` during send, keyboard-focusable buttons with `focus-visible` styles, `prefers-reduced-motion` guard on transitions.
|
|
||||||
- Uses `--trust-high` / `--trust-low` theme CSS custom properties for color consistency.
|
|
||||||
|
|
||||||
_Note: The backend signal endpoint (`POST /api/community/signal`) and seller signal store are gated on cf-orch community postgres landing. The UI degrades gracefully when the endpoint is absent._
|
|
||||||
|
|
||||||
**Forgejo feedback FAB** (floating action button)
|
|
||||||
|
|
||||||
- `FeedbackButton.vue`: floating "Feedback" button in the corner of every view. Opens a two-step modal (type + description → attribution + confirm) that files a Forgejo issue against `Circuit-Forge/snipe`. Hidden when `FORGEJO_API_TOKEN` is unset or in demo mode.
|
|
||||||
- `GET /api/feedback/status` — returns `{"enabled": bool}` so the button never flashes before checking.
|
|
||||||
- `POST /api/feedback` — files the issue; returns `issue_number` and `issue_url`.
|
|
||||||
|
|
||||||
**Live SSE score push** (closes #1)
|
|
||||||
|
|
||||||
- Background enrichment results pushed to the browser via Server-Sent Events as trust scores complete.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [0.1.0] — 2026-03-25
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
Initial beta release of Snipe — eBay listing intelligence and trust scoring.
|
|
||||||
|
|
||||||
- Listing search via eBay scraper (Kasada bypass with headed Chromium + Xvfb).
|
|
||||||
- Trust score composite: feedback rate, negative feedback ratio, member age, zero-feedback penalty.
|
|
||||||
- `TrustScore` dataclass with red flags, partial score flag, composite score (0-100).
|
|
||||||
- Vue 3 SPA frontend: search view, listing card grid, listing detail view, blocklist management.
|
|
||||||
- FastAPI backend: `/api/search`, `/api/enrich`, `/api/blocklist`.
|
|
||||||
- Keyword filtering for search queries.
|
|
||||||
- SQLite persistence via cf-core `db` module.
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "snipe"
|
name = "snipe"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
description = "Auction listing monitor and trust scorer"
|
description = "Auction listing monitor and trust scorer"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
||||||
|
|
@ -128,12 +128,6 @@
|
||||||
>⚑</button>
|
>⚑</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Trust feedback: calm "looks right / wrong" signal buttons -->
|
|
||||||
<TrustFeedbackButtons
|
|
||||||
:seller-id="`ebay::${listing.seller_platform_id}`"
|
|
||||||
:trust="trust"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Price -->
|
<!-- Price -->
|
||||||
<div class="card__price-wrap">
|
<div class="card__price-wrap">
|
||||||
<span
|
<span
|
||||||
|
|
@ -158,7 +152,6 @@ import { computed, ref } from 'vue'
|
||||||
import type { Listing, TrustScore, Seller } from '../stores/search'
|
import type { Listing, TrustScore, Seller } from '../stores/search'
|
||||||
import { useSearchStore } from '../stores/search'
|
import { useSearchStore } from '../stores/search'
|
||||||
import { useBlocklistStore } from '../stores/blocklist'
|
import { useBlocklistStore } from '../stores/blocklist'
|
||||||
import TrustFeedbackButtons from './TrustFeedbackButtons.vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
listing: Listing
|
listing: Listing
|
||||||
|
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
<!-- web/src/components/TrustFeedbackButtons.vue -->
|
|
||||||
<!-- MIT -- component layer -->
|
|
||||||
<template>
|
|
||||||
<div class="trust-feedback" v-if="trust">
|
|
||||||
<template v-if="state === 'idle' || state === 'sending'">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="trust-feedback__btn trust-feedback__btn--confirm"
|
|
||||||
:disabled="state === 'sending'"
|
|
||||||
:aria-busy="state === 'sending'"
|
|
||||||
@click="submitFeedback(true)"
|
|
||||||
>
|
|
||||||
This score looks right
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="trust-feedback__btn trust-feedback__btn--dispute"
|
|
||||||
:disabled="state === 'sending'"
|
|
||||||
:aria-busy="state === 'sending'"
|
|
||||||
@click="submitFeedback(false)"
|
|
||||||
>
|
|
||||||
This score is wrong
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Confirmation -- persistent, no countdown, no urgency -->
|
|
||||||
<p
|
|
||||||
v-else
|
|
||||||
class="trust-feedback__confirmation"
|
|
||||||
role="status"
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
Thanks, noted.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { TrustScore } from '../stores/search'
|
|
||||||
import { useTrustFeedback } from '../composables/useTrustFeedback'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
sellerId: string
|
|
||||||
trust: TrustScore | null
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { state, submitFeedback } = useTrustFeedback(props.sellerId)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.trust-feedback {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trust-feedback__btn {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid currentColor;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
color: inherit;
|
|
||||||
opacity: 0.7;
|
|
||||||
transition: opacity 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.trust-feedback__btn { transition: none; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.trust-feedback__btn:hover:not(:disabled),
|
|
||||||
.trust-feedback__btn:focus-visible {
|
|
||||||
opacity: 1;
|
|
||||||
outline: 2px solid currentColor;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trust-feedback__btn:disabled { cursor: default; }
|
|
||||||
.trust-feedback__btn--confirm { border-color: var(--trust-high, #3fb950); }
|
|
||||||
.trust-feedback__btn--dispute { border-color: var(--trust-low, #f85149); }
|
|
||||||
|
|
||||||
.trust-feedback__confirmation {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// web/src/composables/useTrustFeedback.ts
|
|
||||||
// MIT -- component layer; the API call routes to a BSL endpoint.
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export type FeedbackState = 'idle' | 'sending' | 'confirmed' | 'disputed'
|
|
||||||
|
|
||||||
export function useTrustFeedback(sellerId: string) {
|
|
||||||
const state = ref<FeedbackState>('idle')
|
|
||||||
|
|
||||||
async function submitFeedback(confirmed: boolean): Promise<void> {
|
|
||||||
if (state.value !== 'idle') return
|
|
||||||
state.value = 'sending'
|
|
||||||
try {
|
|
||||||
await fetch('/api/community/signal', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ seller_id: sellerId, confirmed }),
|
|
||||||
})
|
|
||||||
// Always confirm regardless of response -- fail-soft contract.
|
|
||||||
} catch {
|
|
||||||
// Network unreachable -- still confirm to the user. Signal is best-effort.
|
|
||||||
} finally {
|
|
||||||
state.value = confirmed ? 'confirmed' : 'disputed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { state, submitFeedback }
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue