Approve a plan only once its kind, frequency and language are chosen

The plan's title said the kind and frequency were not chosen yet while
its fields showed Summary / per week and the Approve button was on, with
no content language picked. A guessed kind or frequency now shows as not
chosen in the fields too, and Approve waits until they and the language
are set, saying what is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-30 13:49:38 +07:00
parent 74d7e01e4f
commit 7a7f67d3e2
2 changed files with 17 additions and 4 deletions

View File

@@ -396,6 +396,9 @@ const en = {
'agent.languageSet': 'Content will be written in {language}.',
'ins.language': 'Content language',
'ins.languagePending': 'Not chosen yet',
'ins.kindPending': 'Choose a kind',
'ins.perPending': 'Choose how often',
'ins.incomplete': 'Choose the kind, the frequency and the content language first.',
'agent.skipped': 'OK, I skipped “{part}”.',
'agent.removed': 'Removed {rule}.',
'agent.updated': 'Updated: {rule}.',
@@ -899,6 +902,9 @@ const id: Record<Key, string> = {
'agent.languageSet': 'Konten akan ditulis dalam {language}.',
'ins.language': 'Bahasa konten',
'ins.languagePending': 'Belum dipilih',
'ins.kindPending': 'Pilih jenis',
'ins.perPending': 'Pilih frekuensi',
'ins.incomplete': 'Pilih jenis, frekuensi, dan bahasa konten dulu.',
'agent.skipped': 'Oke, “{part}” saya lewati.',
'agent.removed': '{rule} dihapus.',
'agent.updated': 'Diperbarui: {rule}.',

View File

@@ -55,6 +55,8 @@ export function InstructionPage() {
// The whole Brain is attached, so every ready document counts.
const tooFewSources = choosable(docs.data).length < MIN_PRODUCTION_SOURCES && ready.some(i => needsTwoSources(i.rule))
const hasContent = agent.items.some(i => i.rule.type === 'content')
// A kind or frequency the agent only guessed, or no content language yet: the plan is not finished.
const incomplete = ready.some(i => i.rule.type === 'content' && (i.rule.kindGuessed || i.rule.perGuessed)) || (hasContent && !agent.language)
// The agent's current turn: its question, or what it is waiting for. It stays live, so an edit in the plan updates it.
const live = inLang(lang, () => saving
? { text: t('agent.approving'), options: [] as string[] }
@@ -103,7 +105,8 @@ export function InstructionPage() {
async function approve(state = agent, replyLang = lang) {
const items = state.items.filter(i => canSchedule(i.rule))
if (!items.length || tooFewSources) return
const unfinished = items.some(i => i.rule.type === 'content' && (i.rule.kindGuessed || i.rule.perGuessed))
if (!items.length || tooFewSources || unfinished) return
setSaving(true)
const done: Outcome[] = []
const created = new Set<number>()
@@ -198,8 +201,9 @@ export function InstructionPage() {
<b className="rule-sources"><Icon name="brain" />{t('ins.sourcesAll')}</b>
{t('ins.approveNote')}
{tooFewSources && <span className="error">{t('ins.tooFewInBrain', { n: MIN_PRODUCTION_SOURCES })}</span>}
{incomplete && <span className="error">{t('ins.incomplete')}</span>}
</small>
<button className="btn primary" disabled={saving || !idle || tooFewSources} onClick={() => approve()}>
<button className="btn primary" disabled={saving || !idle || tooFewSources || incomplete} onClick={() => approve()}>
<Icon name="check" />{saving ? t('ins.approving') : t('ins.approve', { n: ready.length })}
</button>
</div>
@@ -301,12 +305,15 @@ function RuleCard({ rule, from, onChange, onRemove }: {
<div className="rule-fields">
{rule.type === 'content' ? (
<>
<select aria-label={t('ins.kind')} value={rule.kind} onChange={e => onChange({ kind: e.target.value as ContentKind })}>
{/* A guessed kind or frequency shows as not chosen, as the rule's title says. */}
<select aria-label={t('ins.kind')} value={rule.kindGuessed ? '' : rule.kind} onChange={e => onChange({ kind: e.target.value as ContentKind })}>
{rule.kindGuessed && <option value="" disabled>{t('ins.kindPending')}</option>}
{KINDS.map(k => <option key={k} value={k}>{kindLabel(k)}</option>)}
</select>
<input aria-label={t('ins.count')} type="number" min={1} max={50} value={rule.count}
onChange={e => onChange({ count: Math.max(1, Number(e.target.value) || 1) })} />
<select aria-label={t('ins.frequency')} value={rule.per} onChange={e => onChange({ per: e.target.value as ContentRule['per'] })}>
<select aria-label={t('ins.frequency')} value={rule.perGuessed ? '' : rule.per} onChange={e => onChange({ per: e.target.value as ContentRule['per'] })}>
{rule.perGuessed && <option value="" disabled>{t('ins.perPending')}</option>}
<option value="day">{t('ins.perDay')}</option>
<option value="week">{t('ins.perWeek')}</option>
</select>