feat(web-shell): add scheduled task prompt references - #6589
Conversation
|
Thanks for the PR! (Re-run after review feedback addressed, maintainer verification, and merge conflict resolution.) Template looks good ✓ Problem: feature addition — scheduled task prompts lacked reference insertion controls (extensions, skills, MCP servers) that the chat composer already supports. Users had to manually type Direction: aligned — extending the composer's reference affordances to scheduled tasks is a natural fit. Consistent UX across the product. No CHANGELOG reference needed for this scope. Size: 846 production lines (629 TSX + 183 CSS + 16 i18n + 18 misc), 403 test lines. All in Approach: scope is right — the contenteditable editor, portal-based picker, and reference token serialization are all needed for the feature. The author deferred a few reasonable polish items (RAF throttling, cursor-position insertion, shared utility extraction) to avoid scope creep. The Moving on to code review. 🔍 中文说明感谢贡献!(Review 反馈已处理、maintainer 验证完成、合并冲突已解决后的 re-run。) 模板完整 ✓ 问题:功能新增——定时任务提示词缺少聊天输入框已有的引用插入控件(扩展、技能、MCP 服务器)。用户需要手动输入 方向:对齐——将 composer 的引用能力扩展到定时任务是自然的。产品体验一致。 规模:846 行生产代码(629 TSX + 183 CSS + 16 i18n + 18 其他),403 行测试。全部在 方案:范围合理——contenteditable 编辑器、portal 选择器、引用 token 序列化都是实现该功能所必需的。作者推迟了几项合理的打磨工作(RAF 节流、光标位置插入、共享工具提取)以避免范围蔓延。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code Review (Re-run)Independent proposal: I would have done the same thing — replace the Comparison with the diff: The PR's implementation matches my proposal. Key things done well:
Reuse check: No critical blockers found. The implementation is correct, handles the documented edge cases, and follows the project's conventions. Testing (Re-run)tmux testing note: This PR changes a web UI dialog component (React + contenteditable). Terminal-based tmux testing cannot render or interact with web UI components. The maintainer (wenshao) already performed full Playwright-based visual verification in both dark and light themes with screenshots posted in their verification comment, confirming: reference bar renders, picker floats outside modal, eligibility filtering works, inline tags insert correctly, serialized prompt matches expected output. — Qwen Code · qwen3.7-max |
Reflection (Re-run)This PR has been through multiple rounds of review and iteration. The author addressed all blocking feedback from the review bot, the maintainer verified it end-to-end with Playwright (33/33 tests, screenshots in both themes), and merge conflicts from Looking at the whole picture:
No reservations. This is ready to ship. 中文说明总结 (Re-run)该 PR 经历了多轮 review 和迭代。作者处理了 review bot 的所有阻塞性反馈,maintainer 用 Playwright 端到端验证了它(33/33 测试通过,两种主题下的截图),并干净地解决了 纵观全局:
没有保留意见。可以合并。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
wenshao
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Downgraded from Approve to Comment: CI still running. Suggestion-level recommendations are in the Suggestion summary comment below.
— qwen3.7-max via Qwen Code /review
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
ScheduledTasksDialog.tsx:307-326 |
insertPromptTagElement always appends tag at end, ignoring cursor position. Users inserting a reference mid-prompt see the tag appear at the end. |
Use window.getSelection() to read the current cursor range and insert at that position when the selection is inside the editor; fall back to append when it is not. |
ScheduledTasksDialog.tsx:307 |
Tag insertion bypasses MAX_PROMPT_LENGTH enforcement — typing/paste/drop all check capacity, but programmatic tag insertion does not. Submit guard catches it but editor shows over-length content. |
Add a capacity check in insertPromptTagElement mirroring the insertPlainPromptText pattern, or call normalizePromptEditor after insertion in the effect. |
ScheduledTasksDialog.tsx:265 |
execCommand('insertText') return value discarded — if it fails, pasted/dropped text is silently lost with no fallback. |
Check return value and fall back to manual DOM insertion (create text node, insert at cursor range) when it returns false. |
ScheduledTasksDialog.tsx:1019 |
onInserted={() => setPendingPromptTag(null)} creates new ref each render, listed in useEffect deps — StrictMode double-invocation can insert the same tag twice. |
Stabilize with useCallback(() => setPendingPromptTag(null), []) or add a ref guard in the child to make insertion idempotent. |
ScheduledTasksDialog.tsx:669-672 |
Scroll/resize listeners fire handleReposition on every event with no throttling, calling getComputedStyle + getBoundingClientRect (layout-forcing). |
Wrap in a requestAnimationFrame guard so at most one layout read happens per frame. |
ScheduledTasksDialog.tsx:581 |
loadReferences captures referenceKind via closure — toggle check uses stale value on rapid double-click, causing an unnecessary extra API call. |
Use a ref (referenceKindRef) to read the current referenceKind instead of the closure-captured value. |
ScheduledTasksDialog.tsx:641-650 |
setReferencePickerThemeVars creates new object on every scroll/resize event, unlike setReferencePickerPosition which has structural equality check. |
Add a structural equality check (compare entries) before calling setReferencePickerThemeVars. |
ScheduledTasksDialog.tsx:121-122 |
/word regex matches non-skill tokens like /tmp, /deploy as skill references — visual representation is misleading (text content preserved correctly). |
Validate against loaded skills list before rendering, or require a distinguishing marker (e.g., /skill:name). |
ScheduledTasksDialog.tsx:250-260 |
normalizePromptEditor truncation at MAX_PROMPT_LENGTH may cut a reference token mid-way, producing a broken tag chip on re-parse. |
Strip partial reference tokens at the truncation boundary before re-parsing. |
ScheduledTasksDialog.tsx:929-932 |
Picker item onClick only hides picker (setReferenceKind(null)) but doesn't clear referenceItems/referenceLoading/referenceError — stale items flash briefly when reopening. |
Clear ephemeral picker data (setReferenceItems([]), etc.) alongside hiding, or factor into a dismissPicker() helper. |
ScheduledTasksDialog.tsx:895 |
role="listbox" declared but no arrow-key/Home/End keyboard navigation — ARIA spec mismatch. |
Either implement standard listbox keyboard navigation, or remove the listbox/option roles and let native <button> semantics handle keyboard access. |
ScheduledTasksDialog.tsx:120,150-155 vs useAtMentionMenu.ts:178,224-230 |
AT_REFERENCE_UNSAFE_CHARS, escapeAtReferenceText, unescapeReferenceText are copy-pasted verbatim from useAtMentionMenu.ts. |
Extract into a shared utility (e.g., packages/web-shell/client/utils/atReference.ts) and import from both consumers. |
ScheduledTasksDialog.test.tsx |
Missing test coverage for: error path in loadReferences, picker toggle close, Escape/pointerdown dismissal, onPaste handler, inactive/disabled item filtering, submit-time MAX_PROMPT_LENGTH validation. |
Add targeted tests for each uncovered path. |
— qwen3.7-max via Qwen Code /review
|
Addressed the review comments in Adopted:
Not adopting for this PR: requestAnimationFrame throttling, Validation run:
|
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
ScheduledTasksDialog.tsx (insertPromptTagElement) |
BR element not treated as trailing whitespace — The guard !/\s$/.test(lastText) doesn't account for <br> elements (empty textContent). After pressing Enter, a spurious space is inserted before the tag, producing "hello\n @ext:foo" instead of "hello\n@ext:foo". |
Add !(lastChild instanceof HTMLBRElement) to the condition alongside the existing whitespace check. |
ScheduledTasksDialog.tsx (onCut handler) |
Cut destroys cursor position — After deleteFromDocument(), normalizePromptEditor rebuilds the entire DOM via setPromptEditorText, destroying the browser's caret state. Cursor jumps to position 0 instead of staying at the cut point. |
Skip the full DOM rebuild on cut — perform lighter cleanup (remove empty text nodes, orphaned <br>) or save/restore the caret offset. |
— qwen3.7-max via Qwen Code /review
|
Handled the new blocking comments in Adopted:
Still not adopting the remaining suggestion-summary-only items for this PR: cursor-position insertion, RAF throttling, theme-var equality, full listbox keyboard behavior, shared utility extraction, and extra non-blocking interaction tests. They are either broader polish/refactor work or not required to address the current blocking defects. Validation run on top of the latest
|
|
@wenshao 已处理最新两条 blocking review comments,并回复/resolve 了相关 threads。麻烦有空再帮忙复审一下。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
wenshao
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI failing: review-pr.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge Conflict Resolution SummaryPR: #6589 — feat(web-shell): add scheduled task prompt references ConflictsTwo files had content conflicts, both caused by 1.
|
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
…ksDialog (QwenLM#6748) PR QwenLM#6537 consolidated composerTagIcons.ts into utils/composerTag.ts but PR QwenLM#6589 was branched before that and re-introduced the old import path, breaking the Vite build.







What this PR does
This PR adds reference insertion controls to the scheduled task prompt editor. Users can open extension, skill, and MCP pickers from the task form, choose an item, and insert it as an inline composer-style tag that serializes back into the prompt text used when the task is created or updated.
The picker is rendered as a floating layer outside the modal scroll container, so long lists are not clipped by the dialog body. The prompt editor also normalizes empty editable-content leftovers so clearing the field and inserting a tag does not leave leading whitespace or blank lines.
Why it's needed
Scheduled tasks can now be authored with the same reference affordances as chat prompts, which makes it easier to bind recurring work to installed extensions, model-invocable skills, and MCP servers without manually typing the invocation syntax.
The floating picker and empty-editor cleanup address usability issues found while testing the new controls: the list should not resize or flicker the modal, and inserted tags should appear at the expected cursor position with clean serialized prompt text.
Reviewer Test Plan
How to verify
Open the scheduled task manager and create a new task. In the prompt field, use the extension, skill, and MCP controls to open each picker, select an available item, and confirm the prompt shows inline tags matching the selected references. Create the task and confirm the saved prompt contains the expected invocation text for each selected tag.
While the create-task modal is open, verify that each picker appears as a floating list below the controls and is not clipped by the modal body. Press Escape with the picker open and confirm only the picker closes, not the whole task dialog.
Clear the prompt field after typing, then select a reference. Confirm the tag is inserted at the start of the prompt area without leading blank lines or extra spaces, and confirm submitting the task serializes the prompt without leading whitespace.
Local verification run:
cd packages/web-shell && npx vitest run client/components/dialogs/ScheduledTasksDialog.test.tsx,npm run typecheck,npm run lint, andnpm run buildall passed.Evidence (Before & After)
Before: the scheduled task prompt did not provide inline reference insertion controls, and intermediate picker/editor attempts could be clipped by the modal or preserve empty contenteditable leftovers before inserted tags.
After: the task prompt supports extension, skill, and MCP inline tags; the picker floats outside the modal scroll container; and unit coverage verifies that empty editor leftovers do not add leading whitespace before inserted references.
Tested on
Environment (optional)
Local package verification in the web shell workspace using the repository npm scripts.
Risk & Scope
Linked Issues
N/A
中文说明
What this PR does
这个 PR 为定时任务的提示词编辑器增加了引用插入控件。用户可以在任务表单中打开扩展、技能和 MCP 选择器,选择条目后以内联 composer 风格标签插入,并在创建或更新任务时序列化回实际提示词文本。
选择器现在作为浮层渲染在弹窗滚动容器之外,因此长列表不会被弹窗内容区域裁剪。提示词编辑器也会清理空的可编辑内容残留,避免清空字段后再插入标签时留下前导空格或空行。
Why it's needed
定时任务现在可以使用与聊天提示词一致的引用能力,用户不需要手动输入调用语法,就能更方便地把周期性任务绑定到已安装扩展、可被模型调用的技能和 MCP 服务。
浮层选择器和空编辑器清理修复了新控件测试中发现的可用性问题:列表不应该让弹窗调整高度或闪烁,插入的标签也应该出现在预期光标位置,并产生干净的提示词序列化结果。
Reviewer Test Plan
How to verify
打开定时任务管理并新建任务。在提示词字段中使用扩展、技能和 MCP 控件分别打开选择器,选择可用条目,并确认提示词中显示了对应的内联标签。创建任务后确认保存的提示词包含每个所选标签对应的调用文本。
保持新建任务弹窗打开,确认每个选择器都会作为浮动列表出现在控件下方,并且不会被弹窗内容区域裁剪。选择器打开时按 Escape,确认只关闭选择器,不关闭整个任务弹窗。
输入内容后清空提示词字段,再选择一个引用。确认标签插入到提示词区域起始位置,没有前导空行或额外空格,并确认提交任务时序列化出的提示词没有前导空白。
本地验证已运行:
cd packages/web-shell && npx vitest run client/components/dialogs/ScheduledTasksDialog.test.tsx、npm run typecheck、npm run lint和npm run build均通过。Evidence (Before & After)
Before:定时任务提示词没有内联引用插入控件,并且中间版本的选择器和编辑器可能被弹窗裁剪,或在插入标签前保留 contenteditable 的空内容残留。
After:任务提示词支持扩展、技能和 MCP 内联标签;选择器浮在弹窗滚动容器之外;单测覆盖了空编辑器残留不会在插入引用前产生前导空白。
Tested on
Environment (optional)
在 web shell workspace 中使用仓库 npm scripts 完成本地包级验证。
Risk & Scope
Linked Issues
N/A