Release: dev → main (first public release) #258

Merged
bvandeusen merged 94 commits from dev into main 2026-09-25 10:02:40 -04:00
2 changed files with 56 additions and 15 deletions
Showing only changes of commit b3e4491b0a - Show all commits
@@ -6,9 +6,10 @@
> >
<div class="text-caption fc-muted mb-3"> <div class="text-caption fc-muted mb-3">
Some creators post a cropped fragment on Patreon to say the real thing has Some creators post a cropped fragment on Patreon to say the real thing has
landed in their Discord. FC proposes those pairs; nothing is linked until landed in their Discord. When the creator's own file name settles it —
you accept one. A wrong link would tell you two different pieces are the the same working name on both posts and nowhere else in their library —
same, so this stays a suggestion. FC links the pair for you. Anything less certain waits here, because a
wrong link would tell you two different pieces are the same.
</div> </div>
<v-switch <v-switch
@@ -17,6 +18,17 @@
@update:model-value="store.setEnabled" @update:model-value="store.setEnabled"
/> />
<v-switch
v-model="auto" color="accent" hide-details density="compact"
:disabled="!enabled"
label="Link a pair outright when the file name settles it"
@update:model-value="store.setAuto"
/>
<div class="text-caption fc-muted mb-2">
Off, every pair waits for you here — including the ones there is nothing
left to decide about.
</div>
<v-row class="mt-2"> <v-row class="mt-2">
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<SettingNumberField <SettingNumberField
@@ -26,10 +38,13 @@
:disabled="!enabled" @change="store.setThreshold(Number(threshold))" :disabled="!enabled" @change="store.setThreshold(Number(threshold))"
/> />
<div class="text-caption fc-muted mt-1"> <div class="text-caption fc-muted mt-1">
A pair always needs <strong>two</strong> reasons — being close in time A pair needs <strong>two</strong> reasons — close in time, the post
and the post itself mentioning Discord. Neither is enough alone at any mentioning Discord or the server, or a marker the creator uses on
setting at or above 0.55, which is what stops a busy posting day from both and almost nowhere else. None is enough alone at any setting at
producing false pairs. or above 0.45, which is what stops a busy posting day from producing
false pairs. A shared working name is weighed separately: it says the
two are the same piece rather than that they happened near each
other.
</div> </div>
</v-col> </v-col>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
@@ -57,8 +72,9 @@
</div> </div>
<div v-if="!store.proposals.length" class="text-caption fc-muted"> <div v-if="!store.proposals.length" class="text-caption fc-muted">
Nothing proposed. That is the expected state most of the time — pairs only Nothing waiting. That is the expected state most of the time — a pair the
appear when a post both lands near a drop and says it is about Discord. file names settle is linked without asking, and everything else needs a
post to both land near a drop and point at Discord.
</div> </div>
<div <div
@@ -80,6 +96,19 @@
{{ Math.round(p.score * 100) }}% · {{ Math.round(p.score * 100) }}% ·
timing {{ Math.round((p.signals?.proximity ?? 0) * 100) }}% · timing {{ Math.round((p.signals?.proximity ?? 0) * 100) }}% ·
says so {{ Math.round((p.signals?.declared ?? 0) * 100) }}% says so {{ Math.round((p.signals?.declared ?? 0) * 100) }}%
<template v-if="p.signals?.marker">
· shared marker {{ Math.round(p.signals.marker * 100) }}%
</template>
</div>
<!-- The name, when there is one. A queue that cannot say WHY is one
you learn to click through without reading — and this is the only
signal that claims the two are the SAME piece rather than that
they happened near each other. -->
<div v-if="p.signals?.identity_token" class="text-caption fc-muted">
both call it
<code>{{ p.signals.identity_token }}</code>
({{ Math.round((p.signals.identity ?? 0) * 100) }}% — shared with
another post of theirs, so not conclusive on its own)
</div> </div>
</div> </div>
<v-btn size="small" variant="tonal" @click="store.accept(p.id)">Link</v-btn> <v-btn size="small" variant="tonal" @click="store.accept(p.id)">Link</v-btn>
@@ -98,10 +127,12 @@ import SettingNumberField from '../common/SettingNumberField.vue'
const store = usePostAssociationsStore() const store = usePostAssociationsStore()
const enabled = ref(true) const enabled = ref(true)
const auto = ref(true)
const threshold = ref(0.6) const threshold = ref(0.6)
const windowHours = ref(24) const windowHours = ref(24)
watch(() => store.enabled, (v) => { enabled.value = v }, { immediate: true }) watch(() => store.enabled, (v) => { enabled.value = v }, { immediate: true })
watch(() => store.auto, (v) => { auto.value = v }, { immediate: true })
watch(() => store.threshold, (v) => { threshold.value = v }, { immediate: true }) watch(() => store.threshold, (v) => { threshold.value = v }, { immediate: true })
watch(() => store.windowHours, (v) => { windowHours.value = v }, { immediate: true }) watch(() => store.windowHours, (v) => { windowHours.value = v }, { immediate: true })
+16 -6
View File
@@ -6,14 +6,18 @@ import { useAsyncAction } from '../composables/useAsyncAction.js'
import { toast } from '../utils/toast.js' import { toast } from '../utils/toast.js'
// Backs the announcement review queue (#388 E5): "this Patreon post announced // Backs the announcement review queue (#388 E5): "this Patreon post announced
// that Discord drop". Confirm-only, deliberately — a wrongly-asserted link // that Discord drop". A wrongly-asserted link tells the operator two different
// tells the operator two different pieces are one, which is worse than no link // pieces are one, which is worse than no link at all — so what reaches this
// at all, so accept is the ONLY thing that makes a link real. Mirrors // queue is everything FC is NOT sure enough about to act on by itself.
// seriesSuggestions (FC-6.3), which is the same shape for the same reason. //
// A pair the creator's own working name identifies, where that name is on
// these two posts and nowhere else in their library, is linked without asking
// (`auto`). Mirrors seriesSuggestions (FC-6.3) in shape.
export const usePostAssociationsStore = defineStore('postAssociations', () => { export const usePostAssociationsStore = defineStore('postAssociations', () => {
const api = useApi() const api = useApi()
const proposals = ref([]) const proposals = ref([])
const enabled = ref(true) const enabled = ref(true)
const auto = ref(true)
const threshold = ref(0.6) const threshold = ref(0.6)
const windowHours = ref(24) const windowHours = ref(24)
const { loading, error, run } = useAsyncAction({ errorAs: 'message' }) const { loading, error, run } = useAsyncAction({ errorAs: 'message' })
@@ -28,6 +32,7 @@ export const usePostAssociationsStore = defineStore('postAssociations', () => {
async function loadSettings () { async function loadSettings () {
const s = await api.get('/api/settings/import') const s = await api.get('/api/settings/import')
enabled.value = s.discord_link_enabled enabled.value = s.discord_link_enabled
auto.value = s.discord_link_auto
threshold.value = s.discord_link_threshold threshold.value = s.discord_link_threshold
windowHours.value = s.discord_link_window_hours windowHours.value = s.discord_link_window_hours
} }
@@ -41,6 +46,11 @@ export const usePostAssociationsStore = defineStore('postAssociations', () => {
await saveSettings({ discord_link_enabled: v }) await saveSettings({ discord_link_enabled: v })
} }
async function setAuto (v) {
auto.value = v
await saveSettings({ discord_link_auto: v })
}
async function setThreshold (v) { async function setThreshold (v) {
threshold.value = v threshold.value = v
await saveSettings({ discord_link_threshold: v }) await saveSettings({ discord_link_threshold: v })
@@ -76,8 +86,8 @@ export const usePostAssociationsStore = defineStore('postAssociations', () => {
} }
return { return {
proposals, enabled, threshold, windowHours, loading, error, proposals, enabled, auto, threshold, windowHours, loading, error,
load, loadSettings, setEnabled, setThreshold, setWindowHours, load, loadSettings, setEnabled, setAuto, setThreshold, setWindowHours,
accept, dismiss, rescan accept, dismiss, rescan
} }
}) })