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
12 changes: 8 additions & 4 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,19 @@ such detail into a token. New layout spacing must use the scale.
- Header lead: brand and divider identify the app. Desktop navigation controls
live in the activity rail. Below 768px, Repos and file-sidebar controls
remain in the header because the rail is hidden.
- Activity bar: a fixed `--activity-width` column (48px; 52px for coarse
pointers), spanning the workbench below the header to the shell's bottom inset.
- Activity bar: a fixed `--activity-width` column equal to `--topbar-height`
(36px; 44px for coarse pointers), spanning the workbench below the header to
the shell's bottom inset. The rail mirrors the header rotated: items match
header icon buttons (32px desktop, 44px touch) with the header's inset (2px
desktop, flush touch); coarse pointers add a 4px block inset so flush items
clear the island corners.
Repos (repository icon) followed by Diff (file-diff icon) select one shared panel.
Repos is the initial view.
Clicking the active view toggles its panel; clicking another view opens it.
Keep the rail and selected panel in one rounded island, separated by a 1px
`--border-muted` edge. Views use `--activity-item` square ghost buttons,
20px icons and `--radius-md`; selection uses accent text on `--accent-bg`
with `aria-current="page"`, while `aria-expanded` describes panel visibility.
16px icons, 1.7px strokes and `--radius-control`, like header actions;
selection uses accent text on `--accent-bg` with `aria-current="page"`, while `aria-expanded` describes panel visibility.
Account actions sit at the bottom. Below 768px account actions move
into the header before the rightmost theme control. Below 1012px navigation panels become modal drawers.
- Repos panel: shared navigation width on `--panel`, sharing the file tree
Expand Down
12 changes: 8 additions & 4 deletions apps/web/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ kbd {
flex-direction: column;
justify-content: space-between;
width: var(--activity-width);
padding: var(--space-1) 0;
/* Match the header's inset around its buttons on every edge. */
padding: calc((var(--activity-width) - var(--activity-item)) / 2);
background: transparent;
}
.activity-views,
Expand All @@ -284,11 +285,10 @@ kbd {
.activity-bar button {
width: var(--activity-item);
height: var(--activity-item);
border-radius: var(--radius-md);
}
.activity-bar button svg {
width: var(--icon-lg);
height: var(--icon-lg);
width: var(--icon-base);
height: var(--icon-base);
stroke-width: 1.7;
}
.activity-bar button:hover,
Expand Down Expand Up @@ -1604,4 +1604,8 @@ h2 {
.project-picker-option {
min-height: var(--touch-target);
}
/* Flush touch targets stay inside the island's rounded corners. */
.activity-bar {
padding-block: var(--space-1);
}
}
5 changes: 3 additions & 2 deletions apps/web/src/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@
--diff-gutter: var(--space-3);
--shell-gutter: var(--space-1-5);
--sidebar-width: 330px;
--activity-item: var(--control-large);
--activity-width: calc(var(--activity-item) + 2 * var(--space-1));
/* Rail mirrors the header: its width is the header height, items are header buttons. */
--activity-item: var(--control-height);
--activity-width: var(--topbar-height);
--reading-width: 42.5rem;
--layer-sidebar: 10;
--layer-backdrop: 9;
Expand Down
24 changes: 23 additions & 1 deletion apps/web/test/browser/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,16 @@ test("header icons and sidebar tab indicator use design-system sizes", async ({
if (!topbar || !divider) throw new Error("Missing header divider");
const topbarBox = topbar.getBoundingClientRect();
const dividerBox = divider.getBoundingClientRect();
const find = (selector: string) => {
const element = document.querySelector<HTMLElement>(selector);
if (!element) throw new Error(`Missing ${selector}`);
return element;
};
const railViewButton = find(".activity-bar [aria-current='page']");
const themeElement = find("#theme");
const rail = find(".activity-bar").getBoundingClientRect();
const railView = railViewButton.getBoundingClientRect();
const themeButton = themeElement.getBoundingClientRect();
const indicator = getComputedStyle(tab, "::after");
const colorProbe = document.createElement("span");
colorProbe.style.color = "var(--accent)";
Expand Down Expand Up @@ -1513,20 +1523,32 @@ test("header icons and sidebar tab indicator use design-system sizes", async ({
),
dividerHeight: dividerBox.height,
headerHeight: topbarBox.height,
railWidth: rail.width,
railViewSize: [railView.width, railView.height],
railViewInset: railView.left - rail.left,
headerButtonSize: [themeButton.width, themeButton.height],
headerButtonInset: themeButton.top - topbarBox.top,
railViewRadius: getComputedStyle(railViewButton).borderRadius,
headerButtonRadius: getComputedStyle(themeElement).borderRadius,
accent,
};
});

expect(styles).toMatchObject({
refresh: "16px",
theme: "16px",
view: "20px",
view: "16px",
tabBorderWidth: "1px",
indicatorOpacity: "1",
dividerHeight: 16,
headerHeight: 36,
});
expect(styles.identity).toBe(styles.viewOptions);
// The rail mirrors the header: width equals header height, items match header buttons.
expect(styles.railWidth).toBe(styles.headerHeight);
expect(styles.railViewSize).toEqual(styles.headerButtonSize);
expect(styles.railViewInset).toBe(styles.headerButtonInset);
expect(styles.railViewRadius).toBe(styles.headerButtonRadius);
expect(styles.chromeStrokes.every((stroke) => stroke === "1.7px")).toBe(true);
expect(styles.dividerCenterOffset).toBeLessThanOrEqual(0.5);
expect(styles.indicatorColor).toBe(styles.accent);
Expand Down
Loading