fix(web): align the plan summary's usable-content rule with the tool
planSummary treated whitespace-only content as nameable, so a rejected call whose args survive verbatim rendered a blank active clause beside a live +N. The tool's own rule is trimmed non-empty; the row now uses it. Also hoists the duplicated DSH_SNAPSHOT refresh flag out of the two assembled snapshot files into their shared assembled-boot scaffolding.
This commit is contained in:
@@ -38,17 +38,19 @@ export interface PlanSummary {
|
||||
* the first `in_progress` item and counts the remaining active ones, so a
|
||||
* parallel plan reports how many tasks are running rather than naming one and
|
||||
* hiding the others. `activeContent` is null when nothing is in progress, or
|
||||
* when the first active item carries no usable content — model JSON may. The
|
||||
* row then renders the counts alone rather than falling back to the generic
|
||||
* tool summary: the counts are already known to be good, and the active-item
|
||||
* clause is the only part an unusable name costs.
|
||||
* when the first active item's content is missing, mistyped, or blank once
|
||||
* trimmed — the tool's own rule for usable content, applied here because a
|
||||
* rejected call keeps its args verbatim. The row then renders the counts alone
|
||||
* rather than falling back to the generic tool summary: the counts are already
|
||||
* known to be good, and the active-item clause is the only part an unusable
|
||||
* name costs.
|
||||
* @param todos - the whole list, in model order.
|
||||
* @returns the done/total counts and the two summary halves.
|
||||
*/
|
||||
export function planSummary(todos: readonly PlanItemLike[]): PlanSummary {
|
||||
const active = todos.filter(t => t.status === 'in_progress')
|
||||
const first = active[0]?.content
|
||||
const named = typeof first === 'string' && first !== ''
|
||||
const named = typeof first === 'string' && first.trim() !== ''
|
||||
return {
|
||||
done: todos.filter(t => t.status === 'completed').length,
|
||||
total: todos.length,
|
||||
|
||||
@@ -62,12 +62,16 @@ describe('planSummary', () => {
|
||||
})
|
||||
|
||||
it('has no hint when the first active item carries no usable content (model JSON)', () => {
|
||||
// Unvalidated args: a missing, mistyped, or empty content yields no hint —
|
||||
// and no orphan count, even with a second active item to count.
|
||||
// Unvalidated args: a missing, mistyped, empty, or whitespace-only content
|
||||
// yields no hint — and no orphan count, even with a second active item to
|
||||
// count. Whitespace-only is the tool's own rejection rule (trimmed
|
||||
// non-empty), and a rejected call keeps its args verbatim.
|
||||
expect(planSummary([{ status: 'in_progress' }, { content: 'x', status: 'in_progress' }]))
|
||||
.toMatchObject({ activeContent: null, activeExtra: 0 })
|
||||
expect(planSummary([{ content: 42, status: 'in_progress' }]).activeContent).toBeNull()
|
||||
expect(planSummary([{ content: '', status: 'in_progress' }]).activeContent).toBeNull()
|
||||
expect(planSummary([{ content: ' ', status: 'in_progress' }, { content: 'x', status: 'in_progress' }]))
|
||||
.toMatchObject({ activeContent: null, activeExtra: 0 })
|
||||
})
|
||||
|
||||
it('is empty-safe', () => {
|
||||
|
||||
Reference in New Issue
Block a user