-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: add optional editable task titles #8689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9ce039d
788c6ea
2cd9eaa
f84feec
214eebc
9765932
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||
| type Language, | ||||||||||||||||||||||||||||||||||||||||||||||||
| type GlobalState, | ||||||||||||||||||||||||||||||||||||||||||||||||
| type ClineMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||
| type HistoryItem, | ||||||||||||||||||||||||||||||||||||||||||||||||
| type TelemetrySetting, | ||||||||||||||||||||||||||||||||||||||||||||||||
| type UserSettingsConfig, | ||||||||||||||||||||||||||||||||||||||||||||||||
| TelemetryEventName, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -728,6 +729,57 @@ export const webviewMessageHandler = async ( | |||||||||||||||||||||||||||||||||||||||||||||||
| vscode.window.showErrorMessage(t("common:errors.share_task_failed")) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
| case "setTaskTitle": { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const ids = Array.isArray(message.ids) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? Array.from( | ||||||||||||||||||||||||||||||||||||||||||||||||
| new Set( | ||||||||||||||||||||||||||||||||||||||||||||||||
| message.ids.filter((id): id is string => typeof id === "string" && id.trim().length > 0), | ||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : [] | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (ids.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const rawTitle = message.text ?? "" | ||||||||||||||||||||||||||||||||||||||||||||||||
| const trimmedTitle = rawTitle.trim() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizedTitle = trimmedTitle.length > 0 ? trimmedTitle : undefined | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+745
to
+746
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I’ve drafted a stricter sanitizer that adds three layers of safety:
This ensures that
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const { taskHistory } = await provider.getState() | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!Array.isArray(taskHistory) || taskHistory.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| let hasUpdates = false | ||||||||||||||||||||||||||||||||||||||||||||||||
| const historyById = new Map(taskHistory.map((item) => [item.id, item] as const)) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (const id of ids) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const existingItem = historyById.get(id) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!existingItem) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn(`[setTaskTitle] Unable to locate task history item with id ${id}`) | ||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizedExistingTitle = | ||||||||||||||||||||||||||||||||||||||||||||||||
| existingItem.title && existingItem.title.trim().length > 0 ? existingItem.title.trim() : undefined | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (normalizedExistingTitle === normalizedTitle) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedItem: HistoryItem = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ...existingItem, | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: normalizedTitle, | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| await provider.updateTaskHistory(updatedItem) | ||||||||||||||||||||||||||||||||||||||||||||||||
| hasUpdates = true | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (hasUpdates) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| await provider.postStateToWebview() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| case "showTaskWithId": | ||||||||||||||||||||||||||||||||||||||||||||||||
| provider.showTaskWithId(message.text!) | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1590,7 +1642,6 @@ export const webviewMessageHandler = async ( | |||||||||||||||||||||||||||||||||||||||||||||||
| await updateGlobalState("hasOpenedModeSelector", message.bool ?? true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| await provider.postStateToWebview() | ||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| case "toggleApiConfigPin": | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (message.text) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Defense-in-Depth for Long Titles
I noticed that extremely long titles can visually overlap the edit button in the history sidebar (see screenshot). I recommend a two-pronged approach to handle this:
1. Data Integrity
We can enforce a hard limit here to prevent edge cases or massive strings.
2. CSS truncation
Add the
block truncateclasses to both title spans. This ensures that the titles properly truncate with an ellipsis (...) when they get too long. (see suggestions for TaskHeader.tsx)