fix(issues): UI polish batch — comment input, card gap, board title, activity coalescing
- CommentInput: remove border-t divider, position submit button inside editor area (bottom-right) for cleaner look - CommentCard: add !gap-0 to override Card's default gap-4 - CommentInput/ReplyInput: strip trailing empty lines from markdown before submit to prevent extra blank lines in rendered comments - BoardCard: use normal text color for title instead of muted+hover - Timeline: coalesce same actor + same action within 2 min window, keeping only the final result (e.g. 5 status changes → 1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9e2cb4040f
commit
cd34b82454
5 changed files with 45 additions and 11 deletions
|
|
@ -91,7 +91,7 @@ export function BoardCardContent({
|
|||
|
||||
{/* Title */}
|
||||
<p
|
||||
className={`text-sm font-medium leading-snug line-clamp-2 text-muted-foreground transition-colors group-hover:text-foreground ${showPriority ? "mt-2" : ""}`}
|
||||
className={`text-sm font-medium leading-snug line-clamp-2 ${showPriority ? "mt-2" : ""}`}
|
||||
>
|
||||
{issue.title}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ function CommentCard({
|
|||
collectReplies(entry.id);
|
||||
|
||||
return (
|
||||
<Card className={`!py-0 overflow-hidden${entry.id.startsWith("temp-") ? " opacity-60" : ""}`}>
|
||||
<Card className={`!py-0 !gap-0 overflow-hidden${entry.id.startsWith("temp-") ? " opacity-60" : ""}`}>
|
||||
{/* Parent comment */}
|
||||
<div className="px-4">
|
||||
<CommentRow
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function CommentInput({ onSubmit }: CommentInputProps) {
|
|||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const content = editorRef.current?.getMarkdown()?.trim();
|
||||
const content = editorRef.current?.getMarkdown()?.replace(/(\n\s*)+$/, "").trim();
|
||||
if (!content || submitting) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
|
|
@ -28,8 +28,8 @@ function CommentInput({ onSubmit }: CommentInputProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-card ring-1 ring-border">
|
||||
<div className="min-h-20 max-h-48 overflow-y-auto px-3 py-2">
|
||||
<div className="relative rounded-lg bg-card ring-1 ring-border">
|
||||
<div className="min-h-20 max-h-48 overflow-y-auto px-3 py-2 pb-8">
|
||||
<RichTextEditor
|
||||
ref={editorRef}
|
||||
placeholder="Leave a comment..."
|
||||
|
|
@ -38,7 +38,7 @@ function CommentInput({ onSubmit }: CommentInputProps) {
|
|||
debounceMs={100}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-end border-t border-border/50 px-2 py-1.5">
|
||||
<div className="absolute bottom-1.5 right-1.5">
|
||||
<Button
|
||||
size="icon-sm"
|
||||
disabled={isEmpty || submitting}
|
||||
|
|
|
|||
|
|
@ -209,16 +209,23 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
|
|||
// Watch the global issue store for real-time updates from other users/agents
|
||||
const storeIssue = useIssueStore((s) => s.issues.find((i) => i.id === id));
|
||||
|
||||
const wasLoadedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (storeIssue) {
|
||||
wasLoadedRef.current = true;
|
||||
setIssue(storeIssue);
|
||||
if (!titleFocusedRef.current) {
|
||||
setTitleDraft(storeIssue.title);
|
||||
}
|
||||
} else if (wasLoadedRef.current && !loading) {
|
||||
// Issue was in the store but is now gone (deleted by another user)
|
||||
setIssue(null);
|
||||
}
|
||||
}, [storeIssue]);
|
||||
}, [storeIssue, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
wasLoadedRef.current = false;
|
||||
setIssue(null);
|
||||
setTitleDraft("");
|
||||
setTimeline([]);
|
||||
|
|
@ -461,8 +468,14 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
|
|||
|
||||
if (!issue) {
|
||||
return (
|
||||
<div className="flex flex-1 min-h-0 items-center justify-center text-sm text-muted-foreground">
|
||||
Issue not found
|
||||
<div className="flex flex-1 min-h-0 flex-col items-center justify-center gap-3 text-sm text-muted-foreground">
|
||||
<p>This issue does not exist or has been deleted in this workspace.</p>
|
||||
{!onDelete && (
|
||||
<Button variant="outline" size="sm" onClick={() => router.push("/issues")}>
|
||||
<ChevronLeft className="mr-1 h-3.5 w-3.5" />
|
||||
Back to Issues
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -856,9 +869,30 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
|
|||
}
|
||||
}
|
||||
|
||||
// Coalesce: same actor + same action within 2 min → keep last only
|
||||
const COALESCE_MS = 2 * 60 * 1000;
|
||||
const coalesced: TimelineEntry[] = [];
|
||||
for (const entry of topLevel) {
|
||||
if (entry.type === "activity") {
|
||||
const prev = coalesced[coalesced.length - 1];
|
||||
if (
|
||||
prev?.type === "activity" &&
|
||||
prev.action === entry.action &&
|
||||
prev.actor_type === entry.actor_type &&
|
||||
prev.actor_id === entry.actor_id &&
|
||||
Math.abs(new Date(entry.created_at).getTime() - new Date(prev.created_at).getTime()) <= COALESCE_MS
|
||||
) {
|
||||
// Replace previous with this one (keep the later result)
|
||||
coalesced[coalesced.length - 1] = entry;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
coalesced.push(entry);
|
||||
}
|
||||
|
||||
// Group consecutive activities together so the connector line works
|
||||
const groups: { type: "activities" | "comment"; entries: TimelineEntry[] }[] = [];
|
||||
for (const entry of topLevel) {
|
||||
for (const entry of coalesced) {
|
||||
if (entry.type === "activity") {
|
||||
const last = groups[groups.length - 1];
|
||||
if (last?.type === "activities") {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function ReplyInput({
|
|||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const content = editorRef.current?.getMarkdown()?.trim();
|
||||
const content = editorRef.current?.getMarkdown()?.replace(/(\n\s*)+$/, "").trim();
|
||||
if (!content || submitting) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue