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
33 changes: 16 additions & 17 deletions docker/setup/app.js

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions docker/setup/console.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ html.tc-locked { overflow: hidden; }
/* Names and values that must not part at a hyphen. */
.tc-nowrap { white-space: nowrap; }

/* A row without a menu keeps the menu's place, so actions line up down a list. */
.tc-row-menu-slot { display: block; width: var(--control-sm); height: 1px; flex: none; }
.tc-root[data-density="touch"] .tc-row-menu-slot { width: 32px; }
/* A port's tile says whether it is out on the internet. */
.tc-tile--port-state[data-live] { color: var(--info-text); }

Expand Down Expand Up @@ -157,7 +154,6 @@ html.tc-locked { overflow: hidden; }
@media (max-width: 639.98px) {
.tc-row:has(> .tc-row-actions--wrap) { grid-template-columns: auto minmax(0, 1fr); }
.tc-row > .tc-row-actions--wrap { grid-column: 2 / -1; justify-content: flex-start; }
.tc-row-actions--wrap > .tc-row-menu-slot { display: none; }
/* Only rows without a leading tile put actions under the whole main block.
A stale plain modifier must not move an icon row into a single column. */
.tc-row--plain:not(:has(> .tc-tile, > .tc-row-lead)):has(> .tc-row-actions--wrap) { grid-template-columns: minmax(0, 1fr); }
Expand Down Expand Up @@ -590,7 +586,6 @@ html.tc-locked { overflow: hidden; }
@media (max-width: 639.98px) {
.tc-runtime-channel { grid-template-columns: minmax(0, 1fr); }
.tc-runtime-channel-actions { width: 100%; }
.tc-runtime-channel-actions > .tc-row-menu-slot { display: none; }
.tc-runtime-channel .tc-seg { display: flex; flex: 1 1 auto; }
.tc-runtime-channel .tc-seg button { flex: 1; min-height: 32px; justify-content: center; }
}
Expand Down
4 changes: 4 additions & 0 deletions docker/setup/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Each of these is a bug in the handoff, not a restyle. Keep them when syncing.
The dark input rule sets `background-color` so it preserves `.tc-select`'s
chevron. In forced colours, selects use their native indicator and a system
focus outline, since that mode removes gradients and shadows.
6. **The enabled switch thumb touched its track's right edge.** Its travel
omitted the 2px end inset. Translating by `--_t - 4px` keeps the same 2px
clearance above, below, and at the resting end in both states and sizes.
`scripts/row-ui-audit.js` measures the rendered thumb in each variant.

## Local additions

Expand Down
2 changes: 1 addition & 1 deletion docker/setup/design/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
.tc-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: calc(var(--_t) - 2px); height: calc(var(--_t) - 2px); border-radius: 50%; background: #fff;
box-shadow: 0 1px 2px rgb(0 0 0 / 0.1); transition: translate 150ms; }
.tc-switch[aria-checked="true"] { background: var(--accent); }
.tc-switch[aria-checked="true"]::after { translate: calc(var(--_t) - 2px) 0; }
.tc-switch[aria-checked="true"]::after { translate: calc(var(--_t) - 4px) 0; }

/* listbox */
.tc-listbox { list-style: none; margin: 0; padding: 4px var(--space-2); display: flex; flex-direction: column; gap: 1px; max-height: 280px; overflow: auto; }
Expand Down
Binary file modified docs/media/ports-panel.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 docs/media/setup-dark.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 docs/media/setup-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 19 additions & 1 deletion scripts/row-ui-audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const actions = (update) => `<div class="tc-row-actions tc-row-actions--wrap">${
const main = (title, text) => `<div class="tc-row-main"><div class="tc-row-title"><span class="tc-row-name">${title}</span></div><span class="tc-status tc-status--prose">${text}</span></div>`;
const section = (id, title, row) => `<section class="tc-section" id="${id}"><div class="tc-section-head"><h2 class="tc-section-title">${title}</h2></div><div class="tc-group"><div class="tc-list">${row}</div></div></section>`;
const provider = (update) => `<div class="tc-row tc-row--compact tc-row--plain"><span class="tc-tile tc-tile--icon" aria-hidden="true">${icon}</span><div class="tc-row-main"><div class="tc-row-title"><span class="tc-row-name">Provider updates</span><span class="tc-badge tc-badge--ok">Auto</span><span class="tc-badge">${update ? "3 available" : "Up to date"}</span></div><span class="tc-status tc-status--prose">Providers not checked yet · On · check every 3h · update after 30m idle</span></div>${actions(update)}</div>`;
const switches = (disabled) => [false, true].map((large) => [false, true].map((on) => {
const control = `<button class="tc-switch" type="button" role="switch" aria-label="${large ? "Large" : "Small"} ${on ? "on" : "off"} ${disabled ? "disabled" : "enabled"}" aria-checked="${on}"${disabled ? " disabled" : ""}></button>`;
return large ? `<label class="tc-switch-hit">${control}</label>` : control;
}).join("")).join("");
const fixtures = [
section("reported-row", "Updates", provider(false)),
section("available-row", "Updates available", provider(true)),
Expand All @@ -25,6 +29,7 @@ const fixtures = [
section("connect-row", "With an icon", `<div class="tc-row tc-row--compact"><span class="tc-tile tc-tile--icon">${icon}</span>${main("T3 Connect", "Connect this server to reach it from your other devices.")}<div class="tc-row-actions tc-row-actions--wrap">${button("Connect")}</div></div>`),
section("plain-single", "Single action", `<div class="tc-row tc-row--compact tc-row--plain">${main("Lock console", "Ends this browser’s session. You will need the setup key to come back.")}${button("Lock")}</div>`),
section("plain-empty", "Without an action", `<div class="tc-row tc-row--compact tc-row--plain">${main("Signed in through T3 Code", "This browser is paired with T3 Code.")}</div>`),
...[false, true].map((disabled) => section(`switches-${disabled}`, "Switches", `<div class="tc-row tc-row--compact tc-row--plain">${main(disabled ? "Disabled" : "Enabled", "Both sizes and states.")}<div class="tc-row-actions">${switches(disabled)}</div></div>`)),
].join("");

const geometry = () => {
Expand Down Expand Up @@ -53,6 +58,19 @@ const geometry = () => {
if (r.left < parent.left || r.right > parent.right || r.bottom > parent.bottom) issues.push(`${id}: content outside row`);
}
}
for (const control of document.querySelectorAll(".tc-switch")) {
const track = control.getBoundingClientRect(), thumb = getComputedStyle(control, "::after");
const [x, y = 0] = thumb.translate === "none" ? [0, 0] : thumb.translate.split(/\s+/).map(parseFloat);
const width = parseFloat(thumb.width), height = parseFloat(thumb.height);
const left = parseFloat(thumb.left) + x, top = parseFloat(thumb.top) + y;
const right = track.width - left - width, bottom = track.height - top - height;
const end = control.getAttribute("aria-checked") === "true" ? right : left;
if (![width, height, left, top, right, bottom].every(Number.isFinite)
|| Math.min(left, top, right, bottom) < 0
|| Math.abs(width - height) > 0.5 || Math.abs(top - bottom) > 0.5 || Math.abs(end - top) > 0.5) {
issues.push(`${control.getAttribute("aria-label")}: switch thumb insets must balance (left ${left}, right ${right}, top ${top}, bottom ${bottom})`);
}
}
return issues;
};

Expand Down Expand Up @@ -81,7 +99,7 @@ const geometry = () => {
await frame.evaluate(() => document.fonts.ready);
const issues = await frame.evaluate(geometry);
if (issues.length) failures.push({ tag, issues });
else console.log(`PASS ${tag}: icon/text alignment, action placement, containment (7 row variants)`);
else console.log(`PASS ${tag}: icon/text alignment, action placement, containment (9 row variants), balanced switch thumbs (8 variants)`);
await context.close();
}
} finally { await browser.close(); }
Expand Down
12 changes: 12 additions & 0 deletions scripts/ui-audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,18 @@ const audit = () => {
if (Math.abs(box(main).left - left) > 1) {
add("detached-row-text", "text does not follow the leading icon or row padding", main);
}
// The final control ends at the row's normal inset. Reserving a menu's
// width when there is no menu leaves an unexplained blank strip beside it.
// Phone rows that put actions below the text are intentionally left aligned.
const actions = row.querySelector(":scope > .tc-row-actions");
if (!actions || !visible(actions) || getComputedStyle(actions).justifyContent !== "flex-end") continue;
const controls = [...actions.children].filter((el) => visible(el) && el.matches("button, a, input, select, label, [role=group]"));
if (!controls.length) continue;
const right = Math.max(...controls.map((el) => box(el).right));
const inset = box(row).right - parseFloat(style.borderRightWidth) - parseFloat(style.paddingRight);
if (Math.abs(right - inset) > 1) {
add("trailing-row-space", `final control is ${(inset - right).toFixed(2)}px from the row's right inset`, actions);
}
}

return findings;
Expand Down
Loading