feat: improve task sorting and enhance UI component behavior

- Updated task sorting logic in TeamTaskReader to handle non-numeric IDs with stable lexicographic ordering.
- Refactored ActivityItem to remove unnecessary whitespace for cleaner code.
- Enhanced AttachmentPreviewItem to improve accessibility by adding pointer-events-none class for disabled state.
- Modified MessageComposer to ensure the summary remains compact during message sending.
- Added cleanup logic in useResizableColumns to handle global listeners and styles during drag events.
This commit is contained in:
iliya 2026-03-05 13:32:58 +02:00
parent 524f6f45d2
commit afb0173c05
5 changed files with 24 additions and 5 deletions

View file

@ -271,8 +271,16 @@ export class TeamTaskReader {
} }
} }
// Sort by numeric ID so kanban default order is deterministic (#1, #2, ..., #10, #11) // Sort by numeric ID so kanban default order is deterministic (#1, #2, ..., #10, #11).
tasks.sort((a, b) => Number(a.id) - Number(b.id)); // Fall back to stable lexicographic ordering for unexpected non-numeric IDs.
tasks.sort((a, b) => {
const aIsNumeric = /^\d+$/.test(a.id);
const bIsNumeric = /^\d+$/.test(b.id);
if (aIsNumeric && bIsNumeric) return Number(a.id) - Number(b.id);
if (aIsNumeric) return -1;
if (bIsNumeric) return 1;
return a.id.localeCompare(b.id, undefined, { numeric: true, sensitivity: 'base' });
});
return tasks; return tasks;
} }

View file

@ -176,7 +176,6 @@ function linkifyMentionsInMarkdown(text: string, memberColorMap: Map<string, str
return `${prefix}[@${canonical}](mention://${encodeURIComponent(color)}/${encodeURIComponent(canonical)})`; return `${prefix}[@${canonical}](mention://${encodeURIComponent(color)}/${encodeURIComponent(canonical)})`;
}); });
} }
/** Render `#<digits>` in plain text as clickable inline elements with TaskTooltip. */ /** Render `#<digits>` in plain text as clickable inline elements with TaskTooltip. */
function linkifyTaskIds(text: string, onClick: (taskId: string) => void): React.ReactNode[] { function linkifyTaskIds(text: string, onClick: (taskId: string) => void): React.ReactNode[] {
return text.split(/(#\d+)/g).map((part, i) => { return text.split(/(#\d+)/g).map((part, i) => {

View file

@ -21,7 +21,7 @@ export const AttachmentPreviewItem = ({
return ( return (
<div className="group/att relative flex shrink-0 items-center gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-1.5"> <div className="group/att relative flex shrink-0 items-center gap-2 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-1.5">
{disabled ? ( {disabled ? (
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-md bg-black/50"> <div className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-md bg-black/50">
<Ban size={18} className="text-red-400" /> <Ban size={18} className="text-red-400" />
</div> </div>
) : null} ) : null}

View file

@ -168,7 +168,8 @@ export const MessageComposer = ({
if (!canSend) return; if (!canSend) return;
pendingSendRef.current = true; pendingSendRef.current = true;
const serialized = serializeChipsWithText(trimmed, chipDraft.chips); const serialized = serializeChipsWithText(trimmed, chipDraft.chips);
onSend(recipient, serialized, serialized, attachments.length > 0 ? attachments : undefined); // Summary should stay compact (no expanded chip markdown)
onSend(recipient, serialized, trimmed, attachments.length > 0 ? attachments : undefined);
}, [canSend, recipient, trimmed, onSend, attachments, chipDraft.chips]); }, [canSend, recipient, trimmed, onSend, attachments, chipDraft.chips]);
// Clear draft only after send completes successfully (sending: true → false, no error) // Clear draft only after send completes successfully (sending: true → false, no error)

View file

@ -95,6 +95,17 @@ export function useResizableColumns({
}); });
}, [handlePointerMove, storageKey]); }, [handlePointerMove, storageKey]);
// Safety: if the board unmounts or storageKey changes mid-drag, clean up global listeners/styles.
useEffect(() => {
return () => {
draggingRef.current = null;
document.removeEventListener('pointermove', handlePointerMove);
document.removeEventListener('pointerup', handlePointerUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
}, [handlePointerMove, handlePointerUp]);
const getHandleProps = useCallback( const getHandleProps = useCallback(
(leftColumnId: string) => ({ (leftColumnId: string) => ({
onPointerDown: (e: React.PointerEvent) => { onPointerDown: (e: React.PointerEvent) => {