From 938f59d10d49a33bac97786c08be64182f52cf4f Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Wed, 19 Aug 2026 09:33:11 -0600 Subject: [PATCH] compact todo groups: ride the "+" on the last visible row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In compact layout the add-to-do menu no longer takes a row of its own — it now renders inline on the last visible to-do, saving one line per group. A compact group is guaranteed to have at least one visible to-do (CategoryLane hides it otherwise), so there's always a row to attach it to; a defensive fallback keeps the "+" reachable in the off chance that invariant ever changes. Non-compact layout (popover, AI add, archive, progress pie) is unchanged. --- components/board/group-card.tsx | 238 +++++++++++++++++++------------- 1 file changed, 141 insertions(+), 97 deletions(-) diff --git a/components/board/group-card.tsx b/components/board/group-card.tsx index 7858017..6c36700 100644 --- a/components/board/group-card.tsx +++ b/components/board/group-card.tsx @@ -6,6 +6,7 @@ import { CSS } from "@dnd-kit/utilities"; import { useTheme } from "next-themes"; import { Archive, + ChevronRight, ClipboardList, GripVertical, MoreVertical, @@ -94,6 +95,7 @@ export function GroupCard({ group }: { group: GroupDTO }) { const [todoAiOpen, setTodoAiOpen] = useState(false); const [todoCreateOpen, setTodoCreateOpen] = useState(false); const [statusUpdateOpen, setStatusUpdateOpen] = useState(false); + const [expandedWhileComplete, setExpandedWhileComplete] = useState(false); const { attributes, @@ -143,6 +145,26 @@ export function GroupCard({ group }: { group: GroupDTO }) { // from the list entirely rather than shown crossed-out. const visibleTodos = compact ? group.todos.filter((t) => !t.completed) : group.todos; + // In the default view, a fully-checked-off group collapses down to just + // its header (title, notes icon, kebab menu) with a disclosure triangle + // in place of the drag-grip dots, so a board full of "done" groups + // doesn't stay as visually loud as one still full of open work. The + // triangle re-expands it back to the normal full card. Compact already + // hides finished groups outright (see CategoryLane), so this only + // applies to the default view. + const showDisclosure = !compact && canArchive; + // Reset back to collapsed each time the group freshly becomes fully + // done, rather than remembering a stale expanded choice from its last + // completion cycle. Adjusting state during render (comparing against + // last render's value) instead of in a useEffect avoids an extra render + // pass -- see https://react.dev/learn/you-might-not-need-an-effect. + const [wasComplete, setWasComplete] = useState(canArchive); + if (canArchive !== wasComplete) { + setWasComplete(canArchive); + if (canArchive) setExpandedWhileComplete(false); + } + const isCollapsed = showDisclosure && !expandedWhileComplete; + return ( <>
- - - + {showDisclosure ? ( + + ) : ( + + + + )}

{group.title}

@@ -207,111 +246,116 @@ export function GroupCard({ group }: { group: GroupDTO }) {
- - {compact ? ( - // Defensive fallback -- shouldn't normally happen, since - // CategoryLane already hides a compact group with zero visible - // to-dos, but keeps the "+" reachable if that ever changes. - visibleTodos.length === 0 && ( -
- setTodoCreateOpen(true)} - onAiClick={() => setTodoAiOpen(true)} - aiConfigured={aiConfigured} - /> -
- ) - ) : ( - <> -
- -
+ {canArchive && ( + + )} - {aiConfigured && ( - + + )} - - {canArchive && ( - - )} - - )}