Display titleless tasks consistently across cards and list views without persisting generated fallback titles. - Centralize title, description, and ID display fallback selection. - Bound long description fallbacks to 200 characters with literal dots and preserve full tooltips. - Cover desktop, mobile, whitespace, length-boundary, and explicit-title behavior. Files changed: packages/dashboard/app/components/ListView.tsx | 5 +- packages/dashboard/app/components/TaskCard.css | 12 +++ packages/dashboard/app/components/TaskCard.tsx | 18 +++++- .../app/components/__tests__/ListView.test.tsx | 64 ++++++++++++++++++++++ .../app/components/__tests__/TaskCard.test.tsx | 43 +++++++++++++++ packages/dashboard/app/utils/taskTitleDisplay.ts | 51 +++++++++++++++++ 6 files changed, 188 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-044 Fusion-Task-Lineage: 822f17f3-4839-4f9e-9aa7-cd90037b67de Co-authored-by: Fusion <noreply@runfusion.ai>
52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
import type { Task } from "@fusion/core";
|
|
|
|
export type TaskTitleDisplaySource = "title" | "description" | "id";
|
|
|
|
export interface TaskTitleDisplay {
|
|
source: TaskTitleDisplaySource;
|
|
text: string;
|
|
fullText: string;
|
|
isBoundedDescription: boolean;
|
|
}
|
|
|
|
const MAX_DESCRIPTION_FALLBACK_LENGTH = 200;
|
|
const DESCRIPTION_FALLBACK_SUFFIX = "...";
|
|
|
|
/**
|
|
* Selects a display-only card label without changing the authoritative task data.
|
|
*
|
|
* FNXC:TaskTitleDisplay 2026-08-19-15:22:
|
|
* FN-044 renders an ordinary titleless FN-036 task from its description only after a nonblank
|
|
* persisted title has been ruled out. This UI seam must not restore an AI length policy or persist
|
|
* a fallback title; selected descriptions above 200 characters use a literal `...` within 200 total.
|
|
*/
|
|
export function getTaskTitleDisplay(task: Pick<Task, "id" | "title" | "description">): TaskTitleDisplay {
|
|
if (typeof task.title === "string" && task.title.trim().length > 0) {
|
|
return {
|
|
source: "title",
|
|
text: task.title,
|
|
fullText: task.title,
|
|
isBoundedDescription: false,
|
|
};
|
|
}
|
|
|
|
if (typeof task.description === "string" && task.description.trim().length > 0) {
|
|
const isBoundedDescription = task.description.length > MAX_DESCRIPTION_FALLBACK_LENGTH;
|
|
return {
|
|
source: "description",
|
|
text: isBoundedDescription
|
|
? task.description.slice(0, MAX_DESCRIPTION_FALLBACK_LENGTH - DESCRIPTION_FALLBACK_SUFFIX.length) + DESCRIPTION_FALLBACK_SUFFIX
|
|
: task.description,
|
|
fullText: task.description,
|
|
isBoundedDescription,
|
|
};
|
|
}
|
|
|
|
return {
|
|
source: "id",
|
|
text: task.id,
|
|
fullText: task.id,
|
|
isBoundedDescription: false,
|
|
};
|
|
}
|