From 6d0dee48fa4b9a1cec35e7c0b606bdfad1692828 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 15 Sep 2026 13:30:11 -0400 Subject: [PATCH] feat(board): the No Milestone group collapses, and every group's Done column starts folded (#4077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator, 2026-09-15: the unmilestoned group should collapse, "especially the done column as it currently consume a lot of vertical space for [work] that's already done." - The "No Milestone" group gets the milestones' chevron and collapse, keyed as 0 in the same Set (no milestone id is 0), and starts collapsed on first load when everything in it is finished — the rule finished milestones already follow. - Each group's Done column header becomes a button that folds its cards, with the count still showing and aria-expanded set. Folded by default: done work is the part of a board nobody is reading. - Not persisted, matching the milestone collapse beside it. Milestone 415 step 2. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy --- frontend/src/views/ProjectView.vue | 65 ++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index b98dd36..ae9bdaf 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -179,6 +179,25 @@ const milestoneGroups = computed((): MilestoneGroup[] => { return groups; }); +// The "No Milestone" group has no id, and collapsing it uses the same Set as +// the milestones: 0 stands for it, since no milestone id is ever 0. +const UNASSIGNED_KEY = 0; +function groupKey(group: MilestoneGroup): number { + return group.milestone?.id ?? UNASSIGNED_KEY; +} + +// A group's Done column starts COLLAPSED, to its header and count. Done work is +// the part of a board nobody is reading, and listed in full it pushed the open +// columns of every group below it off the screen (operator, 2026-09-15). Not +// persisted, matching the milestone collapse beside it: each load starts from +// the same defaults. +const expandedDone = ref>(new Set()); +function toggleDone(group: MilestoneGroup) { + const key = groupKey(group); + if (expandedDone.value.has(key)) expandedDone.value.delete(key); + else expandedDone.value.add(key); +} + function toggleMilestoneCollapse(id: number) { if (collapsedMilestones.value.has(id)) { collapsedMilestones.value.delete(id); @@ -386,6 +405,16 @@ async function loadTasks() { all.push(...next.notes); } tasks.value = all; + // The No Milestone group follows the milestone rule: once, on first load, + // it starts collapsed when everything in it is finished. + if (!autoCollapsedOnce.value.has(UNASSIGNED_KEY)) { + autoCollapsedOnce.value.add(UNASSIGNED_KEY); + const assigned = new Set(milestones.value.map((m) => m.id)); + const loose = all.filter((t) => !t.milestone_id || !assigned.has(t.milestone_id)); + if (loose.length && loose.every((t) => t.status === "done" || t.status === "cancelled")) { + collapsedMilestones.value.add(UNASSIGNED_KEY); + } + } } catch { // Say so. This used to swallow the error and leave an empty board, which is // indistinguishable from a project with no tasks — the same "hidden with no @@ -950,11 +979,12 @@ async function confirmDelete() {
- - + +
-
+
@@ -1094,12 +1124,19 @@ async function confirmDelete() {
-
+
-
+ + + +
brings with it. */ +.col-toggle { + width: 100%; + background: none; + border: none; + padding: 0; + font-family: inherit; + cursor: pointer; + text-align: left; +} +.col-toggle:focus-visible { outline: none; box-shadow: var(--fs-focus-ring); border-radius: var(--fs-radius-sm); } .col-count { background: var(--fs-surface-raised); border: 1px solid var(--fs-border-color);