feat: the announcement card offers the auto-link switch and says why (4392)
CI and images / lint (push) Failing after 2s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 20s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Failing after 2m18s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
CI and images / lint (push) Failing after 2s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 20s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Failing after 2m18s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / build-agent (push) Skipped
The card's copy had gone stale against its own backend in three places, which
matters more than usual here because every line of it is a claim about what FC
will do with the operator's library:
* "nothing is linked until you accept one" is no longer true for a pair the
creator's own file name settles. The switch that governs that is now on the
card rather than only in the database.
* "always needs two reasons — close in time and the post mentioning Discord"
left out the shared-marker signal, and named 0.55 as the number below which
no single signal can carry a pair. That number is 0.45 now.
* "pairs only appear when a post lands near a drop and says it is about
Discord" described the empty state as if the identity route did not exist.
A queued row now shows the working name behind it when there is one, and says
in the same breath why that name was not enough on its own. A review queue
that cannot explain itself is one the operator learns to click through without
reading, and this is the only signal that claims the two posts are the SAME
piece rather than that they happened near each other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
@@ -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 })
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user