Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
76b2d4d
Add desktop connect links proposal (working document)
jwhiting Oct 1, 2026
84c9e61
Proposal: state why the setup screen drops step tracking
jwhiting Oct 1, 2026
1200bdd
Proposal: settle prompt, placeholder, Mac recommendation and screen w…
jwhiting Oct 1, 2026
839ec87
Proposal: terminal hyperlinks only in an interactive terminal
jwhiting Oct 1, 2026
f5fad0f
Import desktop setup spec and frames from prerelease PR #387
jwhiting Oct 1, 2026
30c0a1a
Setup screen: show both steps without step tracking
jwhiting Oct 1, 2026
e9ebac1
Specs: connect links, tv links, link output, Mac-first admin guide
jwhiting Oct 1, 2026
b1c5ee7
Revert spec changes; return to the proposal
jwhiting Oct 1, 2026
b733f29
Proposal: dialog precedence, menu on the gate, Clouds appearance, spe…
jwhiting Oct 1, 2026
f25efd1
Proposal: consolidate connection states into one section
jwhiting Oct 1, 2026
b96c113
Proposal: drag strip on dialogs; leave the local page's build approac…
jwhiting Oct 1, 2026
ca60dfe
Proposal: local page appearance follows the window; add task record
jwhiting Oct 1, 2026
8ac42bb
Import visual design from prerelease PR #387
jwhiting Oct 1, 2026
f969215
Adapt the imported design to the proposal
jwhiting Oct 1, 2026
f5698c3
UI specs: setup screen, connection states, system modal, dialog, sizes
jwhiting Oct 1, 2026
c4a661a
Specs: desktop connection flow, product, CLI, guide, glossary
jwhiting Oct 1, 2026
e0ec0e2
Proposal: list differences from PR #387
jwhiting Oct 1, 2026
3e166bb
Spec review round 1 remediation (in progress)
jwhiting Oct 1, 2026
9e31898
Proposal: gate stays until reload; tv links follows the running serve…
jwhiting Oct 1, 2026
7609629
Task record: spec review round 1
jwhiting Oct 1, 2026
6ddfcf5
CLI arch: Writable declares isTTY; record spec review round 2 PASS
jwhiting Oct 1, 2026
fbca5ee
Plan desktop connect links implementation
jwhiting Oct 1, 2026
d6eef62
Connection states: frame them as Television being unable to use its s…
jwhiting Oct 1, 2026
d5168a3
Merge commit 'd6eef624' into thopter/desktop-connect-links-plan
jwhiting Oct 1, 2026
bf850e0
Add connection states explainer
jwhiting Oct 1, 2026
bd1a719
Derive desktop connect links proofs
jwhiting Oct 1, 2026
06becc2
Reference the connection states explainer from its main sources
jwhiting Oct 1, 2026
1478e46
Task record: spec deltas approved at 06becc24
jwhiting Oct 1, 2026
06d9d79
System modal: state the drag strip's two places without the looser si…
jwhiting Oct 1, 2026
62d141a
Task record: proof review round 1
jwhiting Oct 1, 2026
1b26430
Merge reviewed connection-state clarification
jwhiting Oct 1, 2026
b7f3e89
Reconcile desktop connect link proofs after review
jwhiting Oct 1, 2026
edbec2c
Drag strip: wherever no shell is rendered; explainer matches the owni…
jwhiting Oct 1, 2026
358e680
Proposal: rule 9 matches the clarified drag-strip rule
jwhiting Oct 1, 2026
d72f9f3
Task record: proofs converged
jwhiting Oct 1, 2026
22c3b2c
Revise implementation plan from converged proofs
jwhiting Oct 1, 2026
d4c63e5
System modal: local dialogs always wear Clouds; note TV-912
jwhiting Oct 1, 2026
cd9afc4
Licensing: theme material is also attributed in the desktop app's not…
jwhiting Oct 1, 2026
ad230c8
Task record: licensing expansion approved
jwhiting Oct 1, 2026
b016447
Revise desktop connect-links plan after review
jwhiting Oct 1, 2026
ca22ebb
Licensing arch: a theme the desktop app ships names the desktop surface
jwhiting Oct 1, 2026
ad85930
Task record: plan review round 1
jwhiting Oct 1, 2026
68169e1
Merge desktop theme asset-record clarification
jwhiting Oct 1, 2026
940a744
Merge converged proofs and implementation plan
jwhiting Oct 1, 2026
5cebbde
Task record: remaining spec deltas approved; daemon host and verify p…
jwhiting Oct 1, 2026
ef95adf
Task record: Blaxel token scope
jwhiting Oct 1, 2026
241e1c0
Task record: Blaxel token fixed
jwhiting Oct 1, 2026
03d3f12
Implement CLI connect links and plain captured URLs
jwhiting Oct 1, 2026
04fb244
Task record: slice 1 converged
jwhiting Oct 1, 2026
004d4c7
Task record: remove conflict markers left by the slice 1 merge
jwhiting Oct 1, 2026
a3a37a0
Implement served connection states and shared desktop presentation
jwhiting Oct 1, 2026
03e5f43
Apply slice 2 review refinements
jwhiting Oct 1, 2026
4797af3
Consolidate connect-links implementation on the PR branch
jwhiting Oct 1, 2026
fcc7aa8
Checkpoint local setup and main-owned desktop connection lifecycle
jwhiting Oct 1, 2026
5d70395
Exercise setup through native Electron submission and restart
jwhiting Oct 1, 2026
0708bbe
Package the local UI and cover saved-connection recovery
jwhiting Oct 1, 2026
0b1e378
Cross packaged desktop assets, appearance and native drag boundaries
jwhiting Oct 1, 2026
8156ea9
Align the desktop menu contract with Disconnect from Server
jwhiting Oct 1, 2026
c3ece5b
Observe saved startup before releasing real connection checks
jwhiting Oct 1, 2026
09c43be
Check loaded local font faces and complete link parsing coverage
jwhiting Oct 1, 2026
e18ace7
Complete local drag evidence and reconcile desktop proof citations
jwhiting Oct 1, 2026
acc5152
Reject non-web schemes without mistaking them for bare hosts
jwhiting Oct 1, 2026
4cc7fe9
Record the exercised setup and disconnect staging procedure
jwhiting Oct 1, 2026
0704a21
Keep shell presentation assertions across the gate composition boundary
jwhiting Oct 1, 2026
d34a503
Observe the directly composed gate in browser acceptance records
jwhiting Oct 1, 2026
6aef69d
Document slice 3 proof boundaries and tighten staging observations
jwhiting Oct 1, 2026
2e69190
Use the foundation ground locally and align native gate assertions
jwhiting Oct 1, 2026
14a1456
Observe tokenless persistence after forgetting the gated server
jwhiting Oct 1, 2026
901f0fb
Declare the new deliberately unreachable desktop test inputs
jwhiting Oct 1, 2026
601194c
Record passing slice 3 desktop discovery and proof evidence
jwhiting Oct 1, 2026
178e872
Include the shipped Clouds folder in licensing output assertions
jwhiting Oct 1, 2026
04f04e1
Keep gate routing coverage with the application owner
jwhiting Oct 1, 2026
68b8ab1
Record slice 3 validation and review handoff
jwhiting Oct 1, 2026
802889e
Reconcile slice 3 review and test theme resets in the built app
jwhiting Oct 1, 2026
0bf3547
Include bundled Phosphor imports in the desktop inventory assertion
jwhiting Oct 1, 2026
6cd71e7
Merge main and reconcile desktop connect links with toolbar spacing
jwhiting Oct 1, 2026
4e85e73
Archive desktop connect-links proposal and implementation plan
jwhiting Oct 1, 2026
75b0ac7
Keep PR descriptions focused on changes and required evidence
jwhiting Oct 1, 2026
8d2194c
Exclude session details and agent names from PR descriptions
jwhiting Oct 1, 2026
8f4ed72
Write PR guidance around the reader and the purpose of evidence
jwhiting Oct 1, 2026
8e7cd67
Clarify PR evidence and coordination while preserving net-change guid…
jwhiting Oct 1, 2026
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
4 changes: 3 additions & 1 deletion developer-skills/pr-writing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Explain the system change, not merely the changed files or "implements feature X

Organize around the final behavior, architecture, validation, and important decisions. Neither the latest incremental edits nor the sequence of branch work describes the full result the reviewer must assess.

Draft and ready PRs serve the same reader: someone assessing the change without the working conversation. Keep routine coordination in task records or conversation: approvals, review assignments, workflow transitions and pending process steps do not belong in the description. Leave out session details and agent names; when identifying an agent matters, say "an agent".

## Write for the visible diff

Apply [cold-reader](../cold-reader/SKILL.md) and [plain-English](../plain-english-full/SKILL.md), including the cold-reader self-audit search before publishing.
Expand All @@ -21,7 +23,7 @@ Comparisons with the base are useful when both sides appear in the review: "remo

Explain rationale positively: "Library X provides Y." Include it when a reviewer would plausibly wonder about the choice. Rationale that depends on discussing an alternative absent from the final diff belongs in design records or the relevant incremental commit, not the PR body.

State what was actually validated and any material limits. Never imply an unperformed check passed.
State what was actually validated and any material limits, explaining what the evidence establishes about the changed behavior. Include detail when it helps the reader assess confidence or investigate a relevant uncertainty, and retain any check record a spec explicitly requires. Never imply an unperformed check passed.

## Publish exact text

Expand Down
4 changes: 2 additions & 2 deletions developer-skills/tvdev-contribute/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ Several agents may run this skill in different worktrees on one host. Never link

Once the work is complete, perform pre-merge docs prep as the docs spec defines it (`specs/spec-docs.md`, “Pre-merge docs prep”), before treating the contribution as ready to merge into a shared branch. Work-in-progress and draft PRs retain their working documents, including when presented for human spec review. Use that section's step 1 to decide which working documents to archive; add the archive preamble to each and move it to `docs/archive/YYYY-MM/`; delete the rest so `docs/working/` is empty. The archived files appear in the PR so the human sees what you judged worth keeping.

Prepare the PR using [pr-writing](../pr-writing/SKILL.md).
Prepare the PR using [pr-writing](../pr-writing/SKILL.md) and [cold-reader](../cold-reader/SKILL.md). A draft has the same purpose as a ready PR: explain the complete net change and useful verification evidence to a reader outside the working session. Keep routine approvals, review assignments, workflow transitions and pending process steps in task notes or conversation; deleting completed notes during docs prep does not require moving them into the PR. Omit session details and agent names (use "an agent" when needed), while retaining check records explicitly required by a spec.

When presenting the PR, remind the human what their review must establish and make the relevant changes easy to find. Help them perform the minimum PR review defined by spec policy (`specs/spec-policy.md`, “Human PR review”): confirm that every spec delta is fully acceptable and scan the code and test footprint for unexpected scope or complexity. Specs are the product's core authority and slop-free zone; the human must understand and stand behind their changes under the spec ownership rules.
In the conversation accompanying the PR, remind the human what their review must establish and make the relevant changes easy to find. Help them perform the minimum PR review defined by spec policy (`specs/spec-policy.md`, “Human PR review”): confirm that every spec delta is fully acceptable and scan the code and test footprint for unexpected scope or complexity. Specs are the product's core authority and slop-free zone; the human must understand and stand behind their changes under the spec ownership rules.

Make red flags easy to assess: scope expansion, new dependencies, substantial new production or test-harness machinery, and edits to files or behaviors whose connection to the task is unclear. Explain why consequential changes belong and whether their cost is proportionate. This is an impact scan, not a requirement for the human to review every code or test line. Agents remain responsible for detailed correctness, coverage, and verification. Help the human inspect or test the result further when useful.

Expand Down
140 changes: 140 additions & 0 deletions docs/archive/2026-10/desktop-connect-links/implementation-plan.md

Large diffs are not rendered by default.

219 changes: 219 additions & 0 deletions docs/archive/2026-10/desktop-connect-links/proposal.md

Large diffs are not rendered by default.

147 changes: 78 additions & 69 deletions docs/guides/television-admin-guide.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions frames/app/setup.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Setup screen
params:
appearance: { enum: [light, dark], default: light }
state: { enum: [ready, connecting, connected, error], default: ready, description: "ready, connecting, connected, or error" }
script: |
import { setupPrototype, showConnected } from "../lib/setup.ts";

// Staging's stand-in for how long a server takes to answer Connect.
const SIMULATED_CONNECT_MS = 1500;

let cleanup: (() => void) | undefined;
let connectTimer: ReturnType<typeof setTimeout> | undefined;
const wire = () => {
cleanup?.();
clearTimeout(connectTimer);
const screen = document.querySelector<HTMLElement>(".setup-screen");
if (!screen) return;
cleanup = setupPrototype(screen, {
onConnect: () => {
connectTimer = setTimeout(() => showConnected(screen), SIMULATED_CONNECT_MS);
},
});
};
wire();
window.addEventListener("frameset:rendered", wire);
---
<div data-staging-theme="clouds">
{% render '../../specs/ui/setup/setup', state: state %}
</div>
11 changes: 0 additions & 11 deletions frames/app/system-modal.frame

This file was deleted.

6 changes: 6 additions & 0 deletions frames/app/system-modals/browser/access-token.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Access token required
params:
appearance: { enum: [light, dark], default: light }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'unauthorized' %}
6 changes: 6 additions & 0 deletions frames/app/system-modals/browser/cant-connect.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Can’t connect with server
params:
appearance: { enum: [light, dark], default: light }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'error', server_url: 'http://100.101.102.103:32848', seconds: 5 %}
6 changes: 6 additions & 0 deletions frames/app/system-modals/connecting.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Connecting
params:
appearance: { enum: [light, dark], default: light }
---
{% render '../../../specs/ui/app/system-modal/system-modal', state: 'connecting' %}
7 changes: 7 additions & 0 deletions frames/app/system-modals/desktop/access-token.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Access token required
description: "The server's interface inside the desktop app, before it has rendered: no address bar and no drag handle of its own."
params:
appearance: { enum: [light, dark], default: light }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'unauthorized', context: 'desktop', drag_strip: true %}
7 changes: 7 additions & 0 deletions frames/app/system-modals/desktop/cant-connect.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Can’t connect with server
description: "The server's interface inside the desktop app when its first connection fails."
params:
appearance: { enum: [light, dark], default: light }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'error', context: 'desktop', drag_strip: true, server_url: 'http://100.101.102.103:32848', seconds: 5 %}
11 changes: 11 additions & 0 deletions frames/app/system-modals/disconnected.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: Disconnected
params:
appearance: { enum: [light, dark], default: light }
seconds: { type: number, minimum: 0, multipleOf: 1, default: 5, description: "seconds until the next reconnection attempt; 0 shows Reconnecting now" }
---
{% if seconds > 0 %}
{% render '../../../specs/ui/app/system-modal/system-modal', state: 'disconnected', seconds: seconds %}
{% else %}
{% render '../../../specs/ui/app/system-modal/system-modal', state: 'disconnected' %}
{% endif %}
8 changes: 8 additions & 0 deletions frames/app/system-modals/local/access-token.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Access token required
description: "The desktop app's local page when the server rejects the saved connection's token."
params:
appearance: { enum: [light, dark], default: light }
theme: { enum: [clouds], default: clouds }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'unauthorized', context: 'local', drag_strip: true %}
8 changes: 8 additions & 0 deletions frames/app/system-modals/local/cant-connect.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Can’t connect with server
description: "The desktop app's local page when the saved connection's server cannot be reached."
params:
appearance: { enum: [light, dark], default: light }
theme: { enum: [clouds], default: clouds }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'error', context: 'local', drag_strip: true, server_url: 'http://100.101.102.103:32848', seconds: 5 %}
8 changes: 8 additions & 0 deletions frames/app/system-modals/local/connecting.frame
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Connecting
description: "The desktop app's local page, starting a saved connection. It wears Clouds whatever theme the server uses."
params:
appearance: { enum: [light, dark], default: light }
theme: { enum: [clouds], default: clouds }
---
{% render '../../../../specs/ui/app/system-modal/system-modal', state: 'connecting', context: 'local', drag_strip: true %}
96 changes: 96 additions & 0 deletions frames/lib/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Workshop wiring for the setup screen ([[specs/ui/setup/index.md]], Interaction):
// Copy, and Connect's hand-off to the host. Staging only; it binds nothing. At
// rest the markup matches the spec frame's render for the resulting state.

// The copy button's confirmation dwell, stated in its spec's prose
// ([[specs/ui/app/copy-button/index.md]], Testing); no data file carries it.
const COPY_CONFIRM_MS = 1400;

export interface SetupWiringOptions {
/** Connect submitted a non-empty link; the screen is already connecting. */
onConnect?: () => void;
}

const writeClipboard = (text: string): void => {
if (navigator.clipboard) {
void navigator.clipboard.writeText(text).catch(() => {});
return;
}
// Plain HTTP has no clipboard API; copy a selected textarea within the press.
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.append(textarea);
textarea.select();
try {
document.execCommand("copy");
} catch {
// Copying is best-effort in the workshop.
}
textarea.remove();
};

/** Pose the connected state on a wired screen, as its render would. */
export const showConnected = (screen: HTMLElement): void => {
const setupDocument = screen.querySelector<HTMLElement>(".setup-document");
if (setupDocument) setupDocument.inert = true;
screen.querySelector(".setup-done")?.removeAttribute("aria-hidden");
screen.dataset.state = "connected";
};

/** Wire one rendered setup screen. Returns a disposer. */
export const setupPrototype = (screen: HTMLElement, options: SetupWiringOptions = {}): (() => void) => {
const copyButton = screen.querySelector<HTMLButtonElement>(".setup-prompt .copy-button");
const form = screen.querySelector<HTMLFormElement>(".setup-link");
if (!copyButton || !form) {
throw new Error("The setup screen needs its copy button and its link form");
}
const status = copyButton.nextElementSibling as HTMLElement | null;
const field = form.querySelector<HTMLInputElement>("input");
const submit = form.querySelector<HTMLButtonElement>(".setup-submit");
const confirmation = copyButton.querySelector(".copy-button-done")?.textContent?.trim() ?? "";
let confirmTimer: ReturnType<typeof setTimeout> | undefined;

const onCopy = (): void => {
writeClipboard(copyButton.getAttribute("prompt") ?? "");
copyButton.toggleAttribute("copied", true);
if (status) status.textContent = confirmation;
clearTimeout(confirmTimer);
confirmTimer = setTimeout(() => {
copyButton.toggleAttribute("copied", false);
if (status) status.textContent = "";
}, COPY_CONFIRM_MS);
};

const onSubmit = (event: Event): void => {
event.preventDefault();
if (screen.dataset.state !== "ready" && screen.dataset.state !== "error") return;
if (!field?.value.trim()) return;
// Connecting renders without the error treatment: the hint returns and
// the field is no longer invalid.
field.removeAttribute("aria-invalid");
field.removeAttribute("aria-describedby");
const error = screen.querySelector<HTMLElement>("#setup-link-error");
if (error) {
const hint = document.createElement("p");
hint.className = "setup-hint";
hint.innerHTML = 'The link looks like <code>http://…:32848/?token=…</code>';
error.replaceWith(hint);
}
screen.dataset.state = "connecting";
field.disabled = true;
if (submit) submit.disabled = true;
options.onConnect?.();
};

copyButton.addEventListener("click", onCopy);
form.addEventListener("submit", onSubmit);

return () => {
clearTimeout(confirmTimer);
copyButton.removeEventListener("click", onCopy);
form.removeEventListener("submit", onSubmit);
};
};
87 changes: 78 additions & 9 deletions frameset.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"root": ".",
"imports": {
"specs/ui/{app,foundation}/**/*.frame": [
"specs/ui/{app,foundation,setup}/**/*.frame": [
"specs/ui/foundation/index.css",
"specs/ui/foundation/tokens/app.css",
"frames/lib/foundation.ts"
Expand Down Expand Up @@ -35,6 +35,11 @@
"path": "frames/app/app.frame",
"title": "App"
},
{
"type": "frame",
"path": "frames/app/setup.frame",
"title": "Setup screen"
},
{
"type": "folder",
"title": "Onboarding",
Expand Down Expand Up @@ -155,14 +160,78 @@
"title": "Copy button"
},
{
"type": "frame",
"path": "frames/app/system-modal.frame",
"title": "System modal"
},
{
"type": "frame",
"path": "frames/app/upgrade-gate.frame",
"title": "Upgrade gate"
"type": "folder",
"title": "System modals",
"children": [
{
"type": "frame",
"path": "frames/app/system-modals/connecting.frame",
"title": "Connecting"
},
{
"type": "frame",
"path": "frames/app/system-modals/disconnected.frame",
"title": "Disconnected"
},
{
"type": "folder",
"title": "Browser",
"children": [
{
"type": "frame",
"path": "frames/app/system-modals/browser/access-token.frame",
"title": "Access token required"
},
{
"type": "frame",
"path": "frames/app/system-modals/browser/cant-connect.frame",
"title": "Can\u2019t connect with server"
}
]
},
{
"type": "folder",
"title": "Desktop",
"children": [
{
"type": "frame",
"path": "frames/app/system-modals/desktop/access-token.frame",
"title": "Access token required"
},
{
"type": "frame",
"path": "frames/app/system-modals/desktop/cant-connect.frame",
"title": "Can\u2019t connect with server"
}
]
},
{
"type": "folder",
"title": "Desktop local page",
"children": [
{
"type": "frame",
"path": "frames/app/system-modals/local/connecting.frame",
"title": "Connecting"
},
{
"type": "frame",
"path": "frames/app/system-modals/local/access-token.frame",
"title": "Access token required"
},
{
"type": "frame",
"path": "frames/app/system-modals/local/cant-connect.frame",
"title": "Can\u2019t connect with server"
}
]
},
{
"type": "frame",
"path": "frames/app/upgrade-gate.frame",
"title": "Upgrade gate"
}
]
},
{
"type": "frame",
Expand Down
4 changes: 3 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/canonical/styles/canonical/v2/elements/button/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@
padding: var(--space-2);
}

:where(button[size="lg"]) {
font-size: var(--text-lg);
line-height: var(--line-control-lg);
padding: var(--space-8) var(--space-20);
}

:where(button[size="lg"][icon]) {
padding: var(--space-8);
}

/* A sized control sizes the icon it holds: whatever size the composer put on
the icon, the control's own wins — `tv-icon[size]` names a fixed pixel
size, so left alone the icon would ignore the control shrinking around it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
resize: vertical;
}

/* The large size matches a large button beside the field
([[ui/foundation/button/button.css]]): the same text step, line and
vertical padding step, so the two are the same height. */
:where(:is(input:not([type]), input[type=""], input[type="text"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="number"], textarea)[data-size="lg"]) {
font-size: var(--text-lg);
line-height: var(--line-control-lg);
padding: var(--space-8) var(--space-12);
}

/* Shared error-message styling; validation and announcement belong to the surface. */
:where(.tv-error) {
margin: 0;
Expand Down
Loading
Loading