Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1460.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Workflow stage chat renders a message line in full instead of clipping it at 240 characters; the 240-character preview is now used by summaries only, so an owner brief and a Bot reply are readable to the end. ([#1460](https://github.com/ThinkFlowLab/FlyBot/issues/1460))
14 changes: 4 additions & 10 deletions plugins/workflow/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ window.__ModuleLoader__.load({
if (text.startsWith('task-')) return `#${text.slice(-6)}`
return text
}
const CHAT_PREVIEW = 240
function taskSummary(task, run) {
if (run?.taskSummary) return run.taskSummary
const id = shortId(task?.id ?? '')
Expand All @@ -206,11 +205,6 @@ window.__ModuleLoader__.load({
const short = cleanTitle.length > room ? `${cleanTitle.slice(0, room - 1)}…` : cleanTitle
return `${id} · ${short}`
}
function chatPreview(text) {
const line = String(text ?? '').replace(/\s+/g, ' ').trim()
if (line.length <= CHAT_PREVIEW) return line
return `${line.slice(0, CHAT_PREVIEW - 1)}…`
}
function sidebarPreview(item) {
const busy = (item.activeCount ?? 0) > 0 && item.run?.live === true
if (busy) return item.preview || tr('Waiting for a task')
Expand Down Expand Up @@ -425,14 +419,14 @@ window.__ModuleLoader__.load({

function Bubble({ message, workflow, roster }) {
if (message.kind === 'time') return h('div', { className: 'wf-ts' }, message.text)
if (message.kind === 'system') return h('div', { className: `wf-sys ${message.tone ?? ''}`, title: message.text }, chatPreview(message.text))
if (message.kind === 'user') return h('div', { className: 'wf-user', title: message.text }, chatPreview(message.text))
if (message.kind === 'system') return h('div', { className: `wf-sys ${message.tone ?? ''}`, title: message.text }, message.text)
if (message.kind === 'user') return h('div', { className: 'wf-user', title: message.text }, message.text)
const node = nodeOf(workflow, message.botId) ?? { id: message.botId, name: message.botId }
return h('div', { className: 'wf-msg' },
h(Mark, { node, roster, size: 28 }),
h('div', null,
h('div', { className: 'wf-who' }, node.name, message.step ? h('span', { className: 'wf-step' }, tr('Step {step}', { step: message.step })) : null),
h('div', { className: 'wf-bub', title: message.text }, chatPreview(message.text),
h('div', { className: 'wf-bub', title: message.text }, message.text,
message.attachments?.length ? h('div', { className: 'wf-att' }, message.attachments.map(name => h('span', { key: name }, name))) : null)))
}

Expand Down Expand Up @@ -1534,7 +1528,7 @@ window.__ModuleLoader__.load({

return {
name: 'flybot-workflow', inject: ['flybot'], apply, Section, WorkflowSettings,
tr, columnOf, shortId, stageMessages, taskSummary, chatPreview, wirePath, WIRE_SPAN, NODE_WIRE_OUT,
tr, columnOf, shortId, stageMessages, taskSummary, wirePath, WIRE_SPAN, NODE_WIRE_OUT,
runHistoryRows, liveStageRuns, resolveFocusedRun, parallelFrameStyle, NODE_W,
botPaletteSubtitle, botTint,
}
Expand Down
70 changes: 70 additions & 0 deletions plugins/workflow/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,76 @@ test('stage chat hides workflow node Bot lines but keeps owner Bots and system l
assert.deepEqual(visible.map(message => message.id), ['1', '3', '4'])
})

async function stageChatText(messages, roster) {
const graph = delivery()
graph.messages = messages
const call = async (name) => {
if (name === 'workflow/state') return { workflows: [graph], templates: [{ id: 'delivery', name: '发布交付流水线' }] }
return { ok: true, workflows: [graph] }
}
const view = mount(client.Section, { roster, call, actions: {} })
await settle()
findAll(view.tree, node => node.type === 'button' && textOf(node).includes('发布交付流水线'))[0].props.onClick()
await settle()
const text = textOf(view.tree)
view.unmount()
return text
}

function longLine(kind) {
const tail = kind === 'user' ? '让发布说明覆盖四个小节,并保留每条验收命令的原文。'
: kind === 'system' ? '系统提示的每一行都要完整显示,不能丢尾巴。'
: '这是 Chief 的原话,一个字都不能丢。'
const line = `${kind.toUpperCase()} ${tail.repeat(20)}END`
assert.ok(line.length > 240, `the fixture must exceed the 240-character preview cap, got ${line.length}`)
return line
}

function assertFull(text, line) {
assert.equal(text.includes(line), true, `the stage chat dropped part of a ${line.length}-character line`)
assert.equal(text.includes(`${line.slice(0, 239)}…`), false, 'the stage chat still clipped a line at 240 characters')
}

test('a long user line in the stage chat renders in full', async () => {
const long = longLine('user')
const text = await stageChatText([{ id: 'm1', kind: 'user', text: long }], { bots: [] })
assertFull(text, long)
})

test('a long system line in the stage chat renders in full', async () => {
const long = longLine('system')
const text = await stageChatText([{ id: 'm1', kind: 'system', text: long }], { bots: [] })
assertFull(text, long)
})

test('a long roster Bot line in the stage chat renders in full', async () => {
const long = longLine('bot')
const roster = { bots: [], byId: { 'bot-chief': { id: 'bot-chief', name: 'Chief' } } }
const text = await stageChatText([{ id: 'm1', kind: 'bot', botId: 'bot-chief', text: long }], roster)
assertFull(text, long)
})

test('the stage strip still clips a long task title as a summary', async () => {
const graph = delivery()
const longTitle = '让发布说明覆盖四个小节并保留每条验收命令的原文'.repeat(12)
graph.run.task = { id: '#1460', title: longTitle }
delete graph.run.taskSummary
const call = async (name) => {
if (name === 'workflow/state') return { workflows: [graph], templates: [{ id: 'delivery', name: '发布交付流水线' }] }
return { ok: true, workflows: [graph] }
}
const view = mount(client.Section, { roster: { bots: [] }, call, actions: {} })
await settle()
findAll(view.tree, node => node.type === 'button' && textOf(node).includes('发布交付流水线'))[0].props.onClick()
await settle()
const text = textOf(view.tree)
const summary = client.taskSummary({ id: '#1460', title: longTitle })
assert.equal(text.includes(summary), true, 'the stage strip lost its task summary')
assert.equal(text.includes(longTitle), false, 'the stage strip rendered a full task title instead of a summary')
assert.equal(summary.endsWith('…'), true)
view.unmount()
})

test('the hand-a-task modal lists live Tasks board rows only', async () => {
const graph = delivery()
const call = async (name) => {
Expand Down
Loading