Summary
Sidebar.layoutFor() counts every entry in model.recent_projects as a rendered 24px project row, but appendRows() skips rendering any recent project that is already open. Whenever an open project also appears in recent_projects, the layout/scroll math disagrees with the rows actually drawn.
Found by wiring Sidebar.zig's previously-unexecuted tests into CI under #424. These are pre-existing failures that had never run, not regressions.
Evidence (verified on main)
The row producer skips open projects:
Sidebar.zig:507 if (project.isRemote() or isProjectOpen(model, project.path)) continue;
Sidebar.zig:518 if (!project.isRemote() or isProjectOpen(model, project.path)) continue;
The layout consumer does not:
Sidebar.zig:588 .project_count = model.recent_projects.items.len,
and that count drives geometry directly:
Sidebar.zig:485 return @as(i32, @intCast((self.project_count + self.project_heading_count) * 24));
So project_count overcounts by exactly the number of recent projects that are currently open. Section height, every offset derived from it, and scroll clamping are all inflated.
Note :647 and :652 (projectHeadingCount helpers) do apply !isProjectOpen(...), so heading visibility already respects the skip. Only the row count does not, which is why this reads as an oversight rather than an intentional model.
Observed failures
Three tests in Sidebar.zig fail on first execution:
| test |
result |
shared sidebar layout routes every loop row after project rows and scroll |
expected 0, found 1 |
sidebar scroll clamps overflow, shrink, and resize |
expected 334, found 410 |
recent project rows exclude folders already open in the projects list |
expected .open_project, found .quick_chat_overview |
The 334 vs 410 delta is 76px, and the third failure shows hit-testing resolving to the wrong row kind - a click lands on a different row than the one drawn. These are consistent with an overcounted project section, not with three unrelated defects.
App.zig imports Sidebar.zig, so the same three failures surface in the App test binary too.
User-visible impact
With at least one open project also present in recents: the sidebar reserves vertical space for rows that are never drawn, sections below the project list sit lower than the content they label, scroll range over-extends past the last real row, and hit testing can resolve to the wrong row. The third failing test indicates a click intended for a project row can be attributed to a different row kind entirely.
Expected
layoutFor() counts only rows appendRows() will actually render - i.e. apply the same isProjectOpen (and remote/local partition) filtering used at :507/:518, consistent with how :647/:652 already work.
Fix guidance
The three failing tests encode the correct expectations; make them pass rather than adjusting them. Deriving the count from the same predicate used by appendRows (rather than duplicating the condition) would prevent the two from drifting apart again.
Sidebar.zig has 22 tests total and is being wired into the harness by #426, which quarantines these three rather than silently deleting them. Once fixed, remove the quarantine so all 22 gate normally.
Summary
Sidebar.layoutFor()counts every entry inmodel.recent_projectsas a rendered 24px project row, butappendRows()skips rendering any recent project that is already open. Whenever an open project also appears inrecent_projects, the layout/scroll math disagrees with the rows actually drawn.Found by wiring
Sidebar.zig's previously-unexecuted tests into CI under #424. These are pre-existing failures that had never run, not regressions.Evidence (verified on
main)The row producer skips open projects:
The layout consumer does not:
and that count drives geometry directly:
So
project_countovercounts by exactly the number of recent projects that are currently open. Section height, every offset derived from it, and scroll clamping are all inflated.Note
:647and:652(projectHeadingCounthelpers) do apply!isProjectOpen(...), so heading visibility already respects the skip. Only the row count does not, which is why this reads as an oversight rather than an intentional model.Observed failures
Three tests in
Sidebar.zigfail on first execution:shared sidebar layout routes every loop row after project rows and scrollsidebar scroll clamps overflow, shrink, and resizerecent project rows exclude folders already open in the projects list.open_project, found.quick_chat_overviewThe 334 vs 410 delta is 76px, and the third failure shows hit-testing resolving to the wrong row kind - a click lands on a different row than the one drawn. These are consistent with an overcounted project section, not with three unrelated defects.
App.zigimportsSidebar.zig, so the same three failures surface in the App test binary too.User-visible impact
With at least one open project also present in recents: the sidebar reserves vertical space for rows that are never drawn, sections below the project list sit lower than the content they label, scroll range over-extends past the last real row, and hit testing can resolve to the wrong row. The third failing test indicates a click intended for a project row can be attributed to a different row kind entirely.
Expected
layoutFor()counts only rowsappendRows()will actually render - i.e. apply the sameisProjectOpen(and remote/local partition) filtering used at:507/:518, consistent with how:647/:652already work.Fix guidance
The three failing tests encode the correct expectations; make them pass rather than adjusting them. Deriving the count from the same predicate used by
appendRows(rather than duplicating the condition) would prevent the two from drifting apart again.Sidebar.zighas 22 tests total and is being wired into the harness by #426, which quarantines these three rather than silently deleting them. Once fixed, remove the quarantine so all 22 gate normally.