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

+40 -4
View File
@@ -1,4 +1,5 @@
use ely_gpui_component::{
mail::Label,
project::{IssueId, Person, Priority, Status, Task},
theme::{ActiveTheme, TextSize},
};
@@ -7,6 +8,7 @@ use gpui::{
};
use jiff::{Timestamp, ToSpan, civil::Date, tz::TimeZone};
mod boards;
mod tasks;
use super::Page;
@@ -31,6 +33,14 @@ const SCRIPT: &[Step] = &[
Step::UpAt("project-tasks", 26.0, 22.0),
Step::Wait(200),
Step::Shot("task-ticked"),
Step::DownAt("project-board", 80.0, 80.0),
Step::DragTo("project-board", 200.0, 90.0),
Step::DragTo("project-board", 360.0, 110.0),
Step::Wait(250),
Step::Shot("board-drag"),
Step::UpAt("project-board", 360.0, 110.0),
Step::Wait(300),
Step::Shot("board-dropped"),
];
/// What a demo says it did, under it.
@@ -57,6 +67,24 @@ fn team() -> Vec<Person> {
]
}
/// The studio's labels.
fn studio_labels() -> Vec<Label> {
vec![
Label::new("client", "Client", 0),
Label::new("site", "Site", 5),
Label::new("render", "Render", 4),
Label::new("budget", "Budget", 2),
]
}
/// One of the studio's labels, by key.
fn tag(key: &str) -> Label {
studio_labels()
.into_iter()
.find(|label| label.key.as_ref() == key)
.expect("one of the studio's labels")
}
/// A studio's open work around today: late, due soon, in review, done.
fn work(today: Date) -> Vec<Task> {
let day = |offset: i64| today.checked_add(offset.days()).expect("a day nearby");
@@ -72,18 +100,22 @@ fn work(today: Date) -> Vec<Task> {
.status(Status::InProgress)
.priority(Priority::Urgent)
.due(day(-1))
.assignee(who("ana")),
.assignee(who("ana"))
.label(tag("client")),
Task::new("samples", "Order plaster samples")
.issue(IssueId::new("ARC", 44))
.priority(Priority::High)
.due(day(0))
.assignee(who("ben")),
.assignee(who("ben"))
.label(tag("budget")),
Task::new("model", "Photograph the site model")
.issue(IssueId::new("ARC", 38))
.status(Status::InReview)
.priority(Priority::Medium)
.due(day(3))
.assignee(who("chloe")),
.assignee(who("chloe"))
.label(tag("site"))
.label(tag("render")),
Task::new("budget", "Revise the lighting budget")
.issue(IssueId::new("ARC", 47))
.priority(Priority::Low)
@@ -93,7 +125,8 @@ fn work(today: Date) -> Vec<Task> {
.status(Status::Done)
.priority(Priority::Medium)
.due(day(-4))
.assignee(who("dev")),
.assignee(who("dev"))
.label(tag("site")),
Task::new("archive", "Archive last year's renders")
.issue(IssueId::new("ARC", 17))
.status(Status::Backlog),
@@ -111,5 +144,8 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
.child(tasks::fields(window, cx))
.child(tasks::list(window, cx))
.child(tasks::subtasks(window, cx))
.child(boards::kanban(window, cx))
.child(boards::panel(window, cx))
.child(boards::labels(window, cx))
.into_any_element()
}