Eight findings, each terminal case verified in a real terminal first: `\x1b[32mdone\rok\x1b[0m` then `plain` shows `okne` green and `plain` in the DEFAULT color. The replay returned the last written cell's state, so a reset landing after the final write vanished from both the text and the returned state — and every build tool writes exactly that shape, so the color leaked onto all later output. The replay now converges to the state the scan ended in, which is also what it hands to the next line. `abcd\b\x1b[1K|` shows ` |`: CSI 1K erases THROUGH the cursor column, and the loop stopped before it. The erase mode also reads only the first parameter now, since a terminal treats `1;2K` exactly as `1K`. `éx\rYZ` shows `YZ`: a combining mark takes no column, so it attaches to the cell already written instead of advancing the cursor and leaving the `x` standing. `中x\rA` shows `A x`: overwriting a wide character's lead cell leaves its spacer as a blank rather than closing the gap, which would shift everything after it one column left. The banner lost its span when the gutter became padding — a plain block child only reaches the content box, so the reserved column was painted in the body color and the card's top-left radius drawn in it. Invisible in the light theme, where banner and body share a token; visible in the dark one. The header now pulls back across the gutter and re-insets by the same amount. The replay trigger matches the same CSI shape the parser accepts, so a form like `\x1b[1;2K` can no longer skip its own erase, and `replayLine`'s JSDoc documents its new parameter and returned pair. Docs: four places still described the dot as sitting left of the card surface, which stopped being true when the gutter became the card's own padding, and two fixture comments still referenced the exit marker that was deliberately removed.
153 lines
4.5 KiB
CSS
153 lines
4.5 KiB
CSS
/* Geometry mirrors CodeBlock (12px radius, code-block surface + banner rows,
|
|
markdown code-block font) so a terminal card and a fenced code block read as
|
|
one family. The one deliberate divergence: output keeps `white-space: pre`
|
|
and scrolls horizontally, because folding a column-aligned command's output
|
|
destroys its alignment. */
|
|
|
|
.block {
|
|
--dsl-terminal-radius: 12px;
|
|
--dsl-terminal-line-height: 22px;
|
|
/* The card's own left inset, holding the run-state dot in a column of its own
|
|
so it never competes with the commands for horizontal space. */
|
|
--dsl-terminal-gutter: 30px;
|
|
|
|
position: relative;
|
|
margin: 16px 0;
|
|
/* The gutter is the card's OWN padding, not a margin: every consumer rewrites
|
|
`margin` wholesale (each render site sets its own indent), which silently
|
|
cancelled the reservation and let the dot fall outside the card into a
|
|
container that clips it. Owning the reservation here keeps the invariant
|
|
with the component that depends on it. */
|
|
padding-left: var(--dsl-terminal-gutter);
|
|
color: var(--dsw-alias-label-primary);
|
|
background: var(--dsw-alias-markdown-code-block);
|
|
border-radius: var(--dsl-terminal-radius);
|
|
}
|
|
|
|
/* Top-aligned: the status pill and copy control stay on the first prompt row
|
|
however many command lines the card carries. */
|
|
.header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
/* Pulled back across the card's gutter padding so the banner background and
|
|
its top-left radius span the FULL surface, then re-inset by the same amount
|
|
so the prompt text and the dot keep their positions. A plain block child
|
|
only reaches the content box, which left the gutter column painted in the
|
|
body color and drew the card's top-left corner in it — invisible in the
|
|
light theme, where banner and body share a token, and visible in the dark
|
|
one, where they do not. */
|
|
margin-left: calc(-1 * var(--dsl-terminal-gutter));
|
|
padding: 9px 14px 9px var(--dsl-terminal-gutter);
|
|
background: var(--dsw-alias-markdown-code-block-banner);
|
|
border-top-left-radius: var(--dsl-terminal-radius);
|
|
border-top-right-radius: var(--dsl-terminal-radius);
|
|
}
|
|
|
|
/* One row per command line. The prompt column is the only element allowed to
|
|
shrink; the status pill and the copy control keep their intrinsic width. */
|
|
.prompt {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
flex: 1;
|
|
font: var(--dsw-font-markdown-code-block);
|
|
}
|
|
|
|
.promptLine {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
line-height: var(--dsl-terminal-line-height);
|
|
}
|
|
|
|
/* Out of flow inside the card's own gutter padding, so the reservation and the
|
|
dot move together and no consumer margin can pull them apart; the dot neither
|
|
indents its command nor depends on the command's text metrics to line up.
|
|
Centered against the row's line box, not the code font's baseline. */
|
|
.runState {
|
|
position: absolute;
|
|
left: calc(-1 * var(--dsl-terminal-gutter) + 8px);
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
/* The dot is aria-hidden; this is its text label for assistive technology. */
|
|
.runStateLabel {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cwd {
|
|
flex: none;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* `pre`, not `nowrap`: the prompt row renders the command verbatim, and
|
|
`nowrap` collapses the repeated spaces, tabs, and alignment of an indented
|
|
continuation. Both hold the single row and the ellipsis. */
|
|
.command {
|
|
min-width: 0;
|
|
color: var(--dsw-alias-label-primary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: pre;
|
|
}
|
|
|
|
.status {
|
|
flex: none;
|
|
color: var(--dsw-alias-state-error-primary);
|
|
}
|
|
|
|
.copyButton {
|
|
flex: none;
|
|
background-color: transparent;
|
|
border: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
font: var(--dsw-font-xs-13);
|
|
}
|
|
|
|
.output {
|
|
padding: 12px 14px 12px 0;
|
|
font: var(--dsw-font-markdown-code-block);
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
}
|
|
|
|
/* No wrapping, no word-break: alignment is the payload of terminal output. */
|
|
.line {
|
|
min-height: var(--dsl-terminal-line-height);
|
|
white-space: pre;
|
|
}
|
|
|
|
.expand {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 0;
|
|
border: none;
|
|
background-color: transparent;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
cursor: pointer;
|
|
font: inherit;
|
|
text-align: left;
|
|
}
|
|
|
|
.expand:hover {
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.empty {
|
|
padding: 12px 14px 12px 0;
|
|
font: var(--dsw-font-markdown-code-block);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|