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 }) {
+ {visibleTodos.map((todo, index) => {
+ const todoButton = (
+
+ );
- // In compact, the "+" rides along on the last visible row
- // instead of taking a row of its own -- one less line per
- // group. (A compact group always has at least one unchecked
- // to-do -- CategoryLane hides it otherwise -- so there's always
- // a last row to put it on.)
- const isLastRow = compact && index === visibleTodos.length - 1;
+ // In compact, the "+" rides along on the last visible row
+ // instead of taking a row of its own -- one less line per
+ // group. (A compact group always has at least one unchecked
+ // to-do -- CategoryLane hides it otherwise -- so there's
+ // always a last row to put it on.)
+ const isLastRow = compact && index === visibleTodos.length - 1;
- return (
-
- {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 && (
-