Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .boop/changelogs/patch-01kj3a82xept7aw2yb2dtkzefk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Fixed progress bars showing as fully filled or empty

Progress bars throughout the initiative and project detail screens were rendering as binary — either completely empty or completely full — instead of showing actual proportional progress. A project at 15% completion would display an entirely filled bar.

The root cause was that `FillPortion` in Iced only distributes space among sibling elements. Each progress bar had a single child container inside the track, so `FillPortion(15)` behaved identically to `FillPortion(100)` — always taking 100% of the parent.

The fix adds a second container as a sibling to split the space proportionally, with conditional logic to omit the unused portion at 0% and 100% so both edge cases render correctly.

Affected locations:
- Reusable `ProgressBar` widget
- Initiative detail screen (main progress bar and per-project mini bars)
- Project detail screen progress section
- Project card progress bar
116 changes: 66 additions & 50 deletions crates/silo/src/screen/initiative_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,35 +329,43 @@ fn view_progress_section<'a>(
let track_bg = palette.card;
let fill_bg = palette.accent;

// Large progress bar - ensure FillPortion is at least 1 to avoid layout issues
// Large progress bar - conditionally add fill/remaining so 0% and 100% render correctly
let fill_portion = ((percent * 100.0) as u16).max(1);
let progress_bar = container(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: if percent > 0.0 {
Some(Background::Color(fill_bg))
} else {
None
},
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS),
let remaining_portion = (((1.0 - percent) * 100.0) as u16).max(1);
let mut bar_row = iced::widget::Row::new().height(Length::Fill);
if percent > 0.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: Some(Background::Color(fill_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS),
..Default::default()
},
..Default::default()
},
}),
);
}
if percent < 1.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(remaining_portion))
.height(Length::Fill),
);
}
let progress_bar = container(bar_row)
.width(Length::Fill)
.height(Length::Fixed(12.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS),
..Default::default()
}),
)
.width(Length::Fill)
.height(Length::Fixed(12.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS),
},
..Default::default()
},
..Default::default()
});
});

let content = column![
row![
Expand Down Expand Up @@ -446,35 +454,43 @@ fn view_project_card<'a>(
palette.accent
};

// Mini progress bar - ensure FillPortion is at least 1 to avoid layout issues
// Mini progress bar - conditionally add fill/remaining so 0% and 100% render correctly
let fill_portion = ((progress * 100.0) as u16).max(1);
let progress_bar = container(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: if progress > 0.0 {
Some(Background::Color(fill_bg))
} else {
None
},
border: Border {
radius: Radius::from(2.0),
let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1);
let mut bar_row = iced::widget::Row::new().height(Length::Fill);
if progress > 0.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: Some(Background::Color(fill_bg)),
border: Border {
radius: Radius::from(2.0),
..Default::default()
},
..Default::default()
},
}),
);
}
if progress < 1.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(remaining_portion))
.height(Length::Fill),
);
}
let progress_bar = container(bar_row)
.width(Length::Fill)
.height(Length::Fixed(4.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(2.0),
..Default::default()
}),
)
.width(Length::Fill)
.height(Length::Fixed(4.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(2.0),
},
..Default::default()
},
..Default::default()
});
});

// Blocked indicator
let blocked_indicator: Element<'a, Message> = if project.blocked {
Expand Down
58 changes: 33 additions & 25 deletions crates/silo/src/screen/project_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,35 +308,43 @@ fn view_progress_section<'a>(stats: &TaskStats, palette: &'a Palette) -> Element
let track_bg = palette.card;
let fill_bg = palette.accent;

// Progress bar track with fill - ensure FillPortion is at least 1
// Progress bar track with fill - conditionally add fill/remaining so 0% and 100% render correctly
let fill_portion = ((progress * 100.0) as u16).max(1);
let progress_bar = container(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: if progress > 0.0 {
Some(Background::Color(fill_bg))
} else {
None
},
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS_SMALL),
let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1);
let mut bar_row = iced::widget::Row::new().height(Length::Fill);
if progress > 0.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: Some(Background::Color(fill_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS_SMALL),
..Default::default()
},
..Default::default()
},
}),
);
}
if progress < 1.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(remaining_portion))
.height(Length::Fill),
);
}
let progress_bar = container(bar_row)
.width(Length::Fill)
.height(Length::Fixed(6.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS_SMALL),
..Default::default()
}),
)
.width(Length::Fill)
.height(Length::Fixed(6.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(appearance::CORNER_RADIUS_SMALL),
},
..Default::default()
},
..Default::default()
});
});

// Stats text
let stats_text = text(format!(
Expand Down
60 changes: 34 additions & 26 deletions crates/silo/src/widget/progress_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,44 @@ fn progress_bar_inner<'a, Message: 'a>(
let track_bg = p.card;
let fill_bg = p.accent;

// Use layered containers for track and fill - ensure FillPortion is at least 1
// Use FillPortion with two siblings so they split space proportionally
let fill_portion = ((value * 100.0) as u16).max(1);
container(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: if value > 0.0 {
Some(Background::Color(fill_bg))
} else {
None
},
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
let remaining_portion = (((1.0 - value) * 100.0) as u16).max(1);
let mut bar_row = iced::widget::Row::new().height(Length::Fill);
if value > 0.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: Some(Background::Color(fill_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
..Default::default()
},
..Default::default()
},
}),
);
}
if value < 1.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(remaining_portion))
.height(Length::Fill),
);
}
container(bar_row)
.width(Length::Fill)
.height(Length::Fixed(height))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
..Default::default()
}),
)
.width(Length::Fill)
.height(Length::Fixed(height))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
},
..Default::default()
},
..Default::default()
})
.into()
})
.into()
}

/// Convenience function for creating progress bars
Expand Down
58 changes: 33 additions & 25 deletions crates/silo/src/widget/project_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,35 +299,43 @@ fn view_progress_bar<'a>(stats: &TaskStats, palette: &'a Palette) -> Element<'a,
let track_bg = palette.card;
let fill_bg = palette.accent;

// Progress bar track with fill - ensure FillPortion is at least 1
// Progress bar track with fill - conditionally add fill/remaining so 0% and 100% render correctly
let fill_portion = ((progress * 100.0) as u16).max(1);
let progress_bar = container(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: if progress > 0.0 {
Some(Background::Color(fill_bg))
} else {
None
},
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1);
let mut bar_row = iced::widget::Row::new().height(Length::Fill);
if progress > 0.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(fill_portion))
.height(Length::Fill)
.style(move |_| container::Style {
background: Some(Background::Color(fill_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
..Default::default()
},
..Default::default()
},
}),
);
}
if progress < 1.0 {
bar_row = bar_row.push(
container("")
.width(Length::FillPortion(remaining_portion))
.height(Length::Fill),
);
}
let progress_bar = container(bar_row)
.width(Length::Fill)
.height(Length::Fixed(4.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
..Default::default()
}),
)
.width(Length::Fill)
.height(Length::Fixed(4.0))
.style(move |_| container::Style {
background: Some(Background::Color(track_bg)),
border: Border {
radius: Radius::from(CORNER_RADIUS_SMALL),
},
..Default::default()
},
..Default::default()
});
});

// Stats text
let stats_text = text(format!(
Expand Down