Skip to content
Closed
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
Binary file modified apps/ui/e2e/__screens__/agent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/backlog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/bridge-colheads.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/bridge-empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/bridge-prs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/bridge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/done.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/right-sidebar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions apps/ui/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,14 @@ describe("coaching tooltips (RIG-2530 T2)", () => {
);
expect(left).not.toBeNull();
expect(right).not.toBeNull();
// The visible content is a decorative block glyph; the accessible name
// must come from aria-label, never the glyph.
// The visible content is a decorative <Glyph> SVG (aria-hidden, no text);
// the accessible name must come from aria-label, never the glyph.
expect(left?.getAttribute("aria-label")).toBe("Toggle left sidebar");
expect(right?.getAttribute("aria-label")).toBe("Toggle right sidebar");
expect(left?.textContent?.trim()).not.toBe("");
expect(left?.getAttribute("aria-label")).not.toBe(
left?.textContent?.trim(),
);
const glyph = left?.querySelector("svg");
expect(glyph).not.toBeNull();
expect(glyph?.getAttribute("aria-hidden")).toBe("true");
expect(left?.textContent?.trim()).toBe("");
});

test("both sidebar toggles are now live: their coached chords dispatch", async () => {
Expand Down
9 changes: 5 additions & 4 deletions apps/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CoachTipContent,
CoachTipTrigger,
} from "./components/CoachTip";
import { Glyph } from "./components/Glyph";
import { LeftSidebar } from "./components/LeftSidebar";
import { Palette } from "./components/Palette";
import { RightSidebar } from "./components/RightSidebar";
Expand Down Expand Up @@ -77,7 +78,7 @@ const App: Component<RouteSectionProps> = (props) => {
<header class="topbar">
<div class="brand">
<span class="logo" aria-hidden="true">
<Glyph name="logo" />
</span>
<span class="title">Compass</span>
<span class="subtitle">ADE</span>
Expand All @@ -95,7 +96,7 @@ const App: Component<RouteSectionProps> = (props) => {
aria-keyshortcuts={bridgeAria}
>
<span class="tab-glyph" aria-hidden="true">
<Glyph name="status" />
</span>
Bridge
</CoachTipTrigger>
Expand Down Expand Up @@ -146,7 +147,7 @@ const App: Component<RouteSectionProps> = (props) => {
)}
onClick={() => store.toggleLeft()}
>
<Glyph name="panel-left" />
</CoachTipTrigger>
<CoachTipContent
label="Toggle left sidebar"
Expand All @@ -165,7 +166,7 @@ const App: Component<RouteSectionProps> = (props) => {
)}
onClick={() => store.toggleRight()}
>
<Glyph name="panel-right" />
</CoachTipTrigger>
<CoachTipContent
label="Toggle right sidebar"
Expand Down
29 changes: 27 additions & 2 deletions apps/ui/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@
.folder-caret {
color: var(--cx-text-faint);
font-size: 9px;
width: 10px;
width: 11px;
display: inline-flex;
justify-content: center;
/* stylelint-disable-next-line declaration-property-value-disallowed-list -- TODO(motion migration, D9/foundation-T8): raw 0.12s grandfathered by the RIG-2034 cutover lane, which never scoped the motion axis */
Expand Down Expand Up @@ -2261,7 +2261,32 @@
opacity: 0.55;
}

.r-tab .r-tab-icon {
/* The glyph arm is an 11px 1-bit SVG box; the avatar arm is a 15px mono
* letter. They split so the glyph box carries NO font metrics (the SVG is the
* content) and the letter keeps its type. */
.r-tab .r-tab-icon[data-kind="glyph"] {
display: block;
width: 11px;
height: 11px;
/* D2: an odd 11px box centered in the tab's 32px content box (34px −
* 2×1px border, box-sizing: border-box) lands at (32 − 11) / 2 = 10.5px —
* a half pixel that smears every 1px crispEdges cell across two device
* pixels. Whole-integer margins that fill the content box exactly
* (10 + 11 + 11 = 32 per axis) leave zero free space for centering to
* split, so the box's offset is its whole-pixel margin (10px). */
margin: 10px 11px 11px 10px;
}

/* Seat the SVG at the box's top edge. An inline-level replaced box rides the
* parent's text baseline, so the inherited line-height pushes the glyph 2px
* down and out of its box — the whole-pixel margin above would then describe
* the span, not the pixels. state-dot.css carries the same rule for the same
* reason. */
.r-tab .r-tab-icon[data-kind="glyph"] > svg {
display: block;
}

.r-tab .r-tab-icon[data-kind="avatar"] {
font-size: 15px;
line-height: 1;
}
Expand Down
9 changes: 5 additions & 4 deletions apps/ui/src/components/AgentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "../store";
import type { Agent, Terminal } from "../stub-data";
import { ChannelView } from "./ChannelView";
import { Glyph } from "./Glyph";
import { LogPanel } from "./LogPanel";
import { RuntimeMarker } from "./RuntimeMarker";
import { StateDot } from "./StateDot";
Expand Down Expand Up @@ -86,7 +87,7 @@ const PaneView: Component<{ pane: Pane; agent: Agent; focused: boolean }> = (
aria-label="Split right"
onClick={() => splitWith("row")}
>
⊞▏
<Glyph name="split-right" />
</button>
<button
type="button"
Expand All @@ -95,7 +96,7 @@ const PaneView: Component<{ pane: Pane; agent: Agent; focused: boolean }> = (
aria-label="Split down"
onClick={() => splitWith("column")}
>
⊞▁
<Glyph name="split-down" />
</button>
<Show when={props.pane.kind !== "chat"}>
<button
Expand All @@ -105,7 +106,7 @@ const PaneView: Component<{ pane: Pane; agent: Agent; focused: boolean }> = (
aria-label="Close pane"
onClick={() => store.closePane(props.pane.id)}
>
<Glyph name="close" />
</button>
</Show>
</div>
Expand Down Expand Up @@ -265,7 +266,7 @@ export const AgentView: Component = () => {
title={`Close ${tab.title}`}
onClick={() => store.closeTab(tab.id)}
>
<Glyph name="close" />
</button>
</Show>
</div>
Expand Down
45 changes: 45 additions & 0 deletions apps/ui/src/components/Glyph.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, test } from "bun:test";
import { render } from "@solidjs/testing-library";
import { GLYPH_NAMES, Glyph } from "./Glyph";

// The <Glyph/> invariants named by the frozen record (compass-glyph-primitives
// §"The <Glyph/> primitive"): every glyph name yields a non-empty cell list,
// and every cell lies within the 11×11 grid. We reach the geometry through the
// rendered SVG so the assertions bind the observable output, not the table.

function renderedCells(root: Element): Array<[number, number]> {
const cells: Array<[number, number]> = [];
for (const rect of root.querySelectorAll("rect")) {
cells.push([
Number(rect.getAttribute("x")),
Number(rect.getAttribute("y")),
]);
}
return cells;
}

describe("Glyph", () => {
test("is decorative — aria-hidden, no role or label", () => {
const { container } = render(() => <Glyph name="status" />);
const svg = container.querySelector("svg");
expect(svg?.getAttribute("aria-hidden")).toBe("true");
expect(svg?.getAttribute("role")).toBeNull();
expect(svg?.getAttribute("aria-label")).toBeNull();
});

for (const name of GLYPH_NAMES) {
test(`${name} lights cells, all within the 11×11 grid`, () => {
const { container } = render(() => <Glyph name={name} />);
const cells = renderedCells(container);
expect(cells.length).toBeGreaterThan(0);
for (const [x, y] of cells) {
expect(Number.isInteger(x)).toBe(true);
expect(Number.isInteger(y)).toBe(true);
expect(x).toBeGreaterThanOrEqual(0);
expect(x).toBeLessThanOrEqual(10);
expect(y).toBeGreaterThanOrEqual(0);
expect(y).toBeLessThanOrEqual(10);
}
});
}
});
Loading
Loading