CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 2s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 6s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 31s
Build images / build-web (push) Successful in 1m5s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 1m52s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m7s
Rule 27 — E2's other half. The backend can author posts; this is what makes
that visible and adjustable.
**The honesty marker.** A chip on every synthetic post's card: "grouped by
FabledCurator", titled with what it was built from ("Grouped from 4 Discord
messages"). This chip is the only thing standing between "FC assembled this"
and the card reading as something the artist authored, so it keys off nothing
but the flag, and it states the member count rather than just disclosing that
grouping happened — a claim you can check beats a claim you're asked to trust.
A synthetic post has no title on purpose (inventing one is the one place this
feature could put words in a creator's mouth), and the untitled fallback would
otherwise have printed the internal key: "Post fc-drop:99887766". It now names
the post for what it is. There's a test for that specifically.
**The tuning card.** Ingestion & filters gets a Discord-drop-grouping tile:
the switch, the distance cut and the drop window, each with the sentence that
tells the operator which one to reach for. The window's copy says outright
that it is the setting doing most of the work — without it, everything an
artist ever drew of one character collapses into a single post.
Both directions are pinned in postCard.spec.js, including the one that
actually matters: an ordinary post is never marked. Also covered — a post dict
composed before these fields existed degrades to unmarked rather than throwing
on `synthesis.message_count`.
Also fixes the ruff UP017 that failed the lint job on 73eeb7a (timezone.utc →
datetime.UTC, the convention everywhere else in this repo). The integration
suite on that SHA was green: all 12 grouping tests passed and migration 0092
applied cleanly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNXXULQDjVZmbuNa2G9mD9
83 lines
3.5 KiB
Vue
83 lines
3.5 KiB
Vue
<template>
|
|
<MaintenanceTile
|
|
icon="mdi-image-multiple"
|
|
title="Discord drop grouping"
|
|
blurb="Group a creator's variant drop into one post FC writes itself."
|
|
>
|
|
<div v-if="store.settings">
|
|
<div class="text-caption fc-muted mb-3">
|
|
Discord is a delivery channel, not a publisher — one message isn't one
|
|
post. When this is on, FC groups a creator's variant drop (same piece,
|
|
different hair colour or outfit) into a single post it authors, with the
|
|
messages' text as the body. Grouped posts are always marked as FC's own,
|
|
and deleting one returns its messages to the feed unchanged.
|
|
</div>
|
|
|
|
<v-switch
|
|
v-model="local.discord_grouping_enabled" color="accent" hide-details
|
|
density="compact" label="Group Discord drops into posts"
|
|
@update:model-value="onSave"
|
|
/>
|
|
|
|
<v-row class="mt-2">
|
|
<v-col cols="12" sm="6">
|
|
<SettingNumberField
|
|
v-model="local.discord_group_max_distance"
|
|
label="Max visual distance" :min="0" :max="1" :step="0.01"
|
|
density="comfortable" max-width="none"
|
|
:disabled="!local.discord_grouping_enabled" @change="onSave"
|
|
/>
|
|
<div class="text-caption fc-muted mt-1">
|
|
0 is identical, 1 is unrelated. Lower groups less. Raising this is
|
|
the fix when a drop comes out scattered across several posts —
|
|
but raise it slowly: too high merges pieces that only look alike.
|
|
</div>
|
|
</v-col>
|
|
<v-col cols="12" sm="6">
|
|
<SettingNumberField
|
|
v-model="local.discord_group_window_minutes"
|
|
label="Drop window (minutes)" :min="1" :step="5"
|
|
density="comfortable" max-width="none"
|
|
:disabled="!local.discord_grouping_enabled" @change="onSave"
|
|
/>
|
|
<div class="text-caption fc-muted mt-1">
|
|
The quiet gap that ends a drop, measured between consecutive
|
|
messages — so variants trickling out over an evening stay one post.
|
|
This is the setting doing most of the work: without it, everything
|
|
an artist ever drew of one character would collapse into one post.
|
|
</div>
|
|
</v-col>
|
|
</v-row>
|
|
</div>
|
|
<div v-else><v-skeleton-loader type="paragraph" /></div>
|
|
</MaintenanceTile>
|
|
</template>
|
|
|
|
<script setup>
|
|
// #388 E2. Every value here is operator-tunable because the quality bar is a
|
|
// judgement no test can settle: grouping too greedy merges distinct pieces,
|
|
// too shy leaves a drop scattered. The two failure modes are not symmetric —
|
|
// scattered is visible and fixable, a wrong merge is a post asserting that
|
|
// unrelated art belongs together — so the shipped default sits on the tight
|
|
// side and this card is how it gets loosened.
|
|
import { reactive, watch } from 'vue'
|
|
|
|
import { useSettingSave } from '../../composables/useSettingSave.js'
|
|
import { useMLStore } from '../../stores/ml.js'
|
|
import MaintenanceTile from '../common/MaintenanceTile.vue'
|
|
import SettingNumberField from '../common/SettingNumberField.vue'
|
|
|
|
const store = useMLStore()
|
|
const { save } = useSettingSave(store.patchSettings)
|
|
const local = reactive({})
|
|
watch(() => store.settings, (s) => { if (s) Object.assign(local, s) }, { immediate: true })
|
|
|
|
function onSave() {
|
|
save({
|
|
discord_grouping_enabled: Boolean(local.discord_grouping_enabled),
|
|
discord_group_max_distance: Number(local.discord_group_max_distance),
|
|
discord_group_window_minutes: Number(local.discord_group_window_minutes),
|
|
})
|
|
}
|
|
</script>
|