Project · Issues and boards: a board, issue cards, a task's panel, labels

KanbanBoard lays columns of cards side by side. A card drags to a
place in any column: it rides raised at the pointer, a slot opens where
it would land, the other cards glide aside on motion::Flip, and the
drop asks the owner through on_move(card, column, index); a drop in its
own place asks nothing. Option with an arrow moves the focused card,
whose focus the board keys by card so it follows it across columns. A
press, Enter or Space opens it. Sprint Board is this board.

IssueCard shows an issue's status, name, assignee, title, priority,
labels and due day. TaskDetailPanel lays a task out in full: its title
edits in place, its status, priority, assignee and due day change where
they stand, and its notes and steps follow; each edit hands the whole
task on, and a blank title is not taken. LabelManager tends a team's
labels: a hue picked from a popover, a name edited in place, a delete,
and a field that makes a new one; names stay unique in any case, and a
refused name says why.

The fill rule changes: min_w_full, taken up in T30d, resolves against
a block's outer width in taffy 0.9, so a field in a padded card ran
past its padding. A component that fills now takes no width on its
root, which blocks and flex columns stretch; FormField and the new
roots do. The chart's hue names move to theme::HUE_NAMES, which the
event editor and the label manager share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-27 05:12:20 -04:00
1 parent 9b506e5d54
commit 62382f7e68
24 files changed
+1813 -31

No files matched your search

+35 -2
View File
@@ -3,7 +3,7 @@ use std::fmt;
use gpui::SharedString;
use jiff::civil::Date;
use crate::primitives::IconName;
use crate::{mail::Label, primitives::IconName};
/// Where a task stands in its workflow.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -83,6 +83,15 @@ pub enum Priority {
}
impl Priority {
/// Most urgent first.
pub const ALL: [Priority; 5] = [
Priority::Urgent,
Priority::High,
Priority::Medium,
Priority::Low,
Priority::None,
];
pub fn words(self) -> &'static str {
match self {
Priority::None => "No priority",
@@ -93,6 +102,23 @@ impl Priority {
}
}
pub(crate) fn key(self) -> &'static str {
match self {
Priority::None => "none",
Priority::Low => "low",
Priority::Medium => "medium",
Priority::High => "high",
Priority::Urgent => "urgent",
}
}
pub(crate) fn of_key(key: &str) -> Priority {
Priority::ALL
.into_iter()
.find(|priority| priority.key() == key)
.unwrap_or_else(|| panic!("no priority {key}"))
}
/// Bars lit of three; urgent has a mark of its own.
pub(crate) fn bars(self) -> usize {
match self {
@@ -146,7 +172,7 @@ impl Person {
}
}
/// A piece of work: its key, its issue's name if it has one, what it is, where it stands, how urgent, when it is due, and who has it.
/// A piece of work: its key, its issue's name if it has one, what it is, where it stands, how urgent, when it is due, who has it, and its labels.
#[derive(Clone, Debug, PartialEq)]
pub struct Task {
pub key: SharedString,
@@ -156,6 +182,7 @@ pub struct Task {
pub priority: Priority,
pub due: Option<Date>,
pub assignee: Option<Person>,
pub labels: Vec<Label>,
}
impl Task {
@@ -168,6 +195,7 @@ impl Task {
priority: Priority::None,
due: None,
assignee: None,
labels: Vec::new(),
}
}
@@ -196,6 +224,11 @@ impl Task {
self
}
pub fn label(mut self, label: Label) -> Self {
self.labels.push(label);
self
}
/// Due before `today` and still open.
pub fn overdue(&self, today: Date) -> bool {
!self.status.closed() && self.due.is_some_and(|due| due < today)