refactor(ui): share per-message affordance CSS via chat.css (#549)

The per-message rewind / edit / retry affordance — the icon glyphs
(.icon-edit / .icon-rewind / .icon-retry) plus the inline edit-in-place
form (.msg-edit-*) and the [data-busy] / .msg-editing states — was
duplicated verbatim in both pane stylesheets: ui/static/style.css
(interactive) and console/static/coordinator/coordinator.css
(coordinator). PR #598 deferred consolidating them to keep that
coord-only change off the shipped interactive stylesheet's cascade.

Move the block into shared_static/chat.css, immediately after the
.msg-actions / .msg-action-btn primitives both panes already share, and
delete both copies (including coordinator.css's now-obsolete FOLLOW-UP
note describing the duplication).

Both index.html files load chat.css before their pane stylesheet, so the
rules land earlier in the cascade; the selectors are unique (defined
nowhere else, confirmed repo-wide) so it is a visual no-op. The block is
moved verbatim — chat.css's sibling rules use a different variable
vocabulary (--r-sm=4px / --font-mono) than the affordance block
(--radius-sm=3px / --font-ui), so renaming would change radii/fonts.

Verified pixel-identical via a headless-Chrome render-diff of both panes,
before vs after, across all four affordance states (edit+rewind, retry,
editing-open, busy): 0 differing pixels.
This commit is contained in:
Patrick Buckley
2026-05-28 21:18:31 -07:00
parent 371830a428
commit 06c52e8e7e
3 changed files with 169 additions and 339 deletions
@@ -647,176 +647,3 @@
font-size: 13px;
}
}
/* ==========================================================================
Per-message rewind / edit / retry affordance (#549)
The .msg-actions / .msg-action-btn primitives live in shared/chat.css; the
icon glyphs + inline edit-in-place form currently live only in interactive's
ui/static/style.css, so they're ported here verbatim (same CSS variables
from shared/base.css).
FOLLOW-UP (deferred from PR B Phase 2): this duplicates ui/static/style.css.
Both copies should be promoted into shared/chat.css (alongside .msg-actions)
so the two panes share one source of truth. Deferred to keep this coord-only
phase from re-touching the shipped interactive stylesheet's cascade; do it as
a focused both-pane change with its own browser smoke.
========================================================================== */
/* Icon: retry (circular arrow) */
.icon-retry {
width: 13px;
height: 13px;
border: 1.5px solid currentColor;
border-radius: 50%;
border-bottom-color: transparent;
position: relative;
}
.icon-retry::after {
content: "";
position: absolute;
bottom: -1px;
right: -1px;
width: 0;
height: 0;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
border-top: 3px solid currentColor;
transform: rotate(-30deg);
}
/* Icon: edit (pencil) */
.icon-edit {
width: 12px;
height: 12px;
position: relative;
transform: rotate(-45deg);
}
.icon-edit::before {
content: "";
position: absolute;
top: 0;
left: 3px;
width: 6px;
height: 8px;
border: 1.5px solid currentColor;
border-radius: 1px 1px 0 0;
box-sizing: border-box;
}
.icon-edit::after {
content: "";
position: absolute;
bottom: 0;
left: 3px;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid currentColor;
}
/* Icon: rewind (chevrons pointing left) */
.icon-rewind {
width: 14px;
height: 12px;
position: relative;
}
.icon-rewind::before,
.icon-rewind::after {
content: "";
position: absolute;
top: 1px;
width: 6px;
height: 6px;
border-left: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
}
.icon-rewind::before {
left: 1px;
}
.icon-rewind::after {
left: 6px;
}
/* Edit-in-place form */
.msg-edit-form {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.msg-edit-textarea {
width: 100%;
min-height: 40px;
max-height: 200px;
background: var(--bg);
color: var(--fg-bright);
border: 1px solid var(--accent-dim);
border-radius: var(--radius-sm);
padding: 8px 10px;
font-family: var(--font-ui);
font-size: 14px;
line-height: 1.5;
resize: vertical;
outline: none;
transition: border-color 0.12s ease;
box-sizing: border-box;
}
.msg-edit-textarea:focus {
border-color: var(--accent);
}
.msg-edit-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
}
.msg-edit-btn {
padding: 4px 14px;
font-size: 12px;
font-family: var(--font-ui);
font-weight: 500;
border-radius: var(--radius-sm);
cursor: pointer;
border: 1px solid var(--border-strong);
background: var(--bg);
color: var(--fg);
transition:
background 0.1s ease,
border-color 0.1s ease,
color 0.1s ease;
}
.msg-edit-btn:hover {
background: var(--bg-highlight);
}
.msg-edit-btn-send {
background: var(--accent-dim);
color: var(--accent);
border-color: var(--accent);
}
.msg-edit-btn-send:hover {
background: var(--accent);
color: #fff;
}
/* Edit-in-place active state */
.msg-editing {
background: var(--bg-surface);
border-color: var(--accent-dim);
}
.msg-editing .msg-actions {
display: none;
}
/* Busy-state disables action buttons */
[data-busy="true"] .msg-action-btn {
opacity: 0.3;
pointer-events: none;
cursor: not-allowed;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.msg-edit-textarea,
.msg-edit-btn {
transition: none;
}
}
+169
View File
@@ -1250,6 +1250,175 @@
}
}
/* ==========================================================================
Per-message rewind / edit / retry affordance (#549)
Icon glyphs rendered inside the .msg-action-btn toolbar above, plus the
inline edit-in-place form. Shared by the interactive pane (ui/static) and
the coordinator dashboard (console/static/coordinator); both load this file
before their page stylesheet, so this is the single source of truth.
Vars resolve from shared/base.css (loaded by both panes).
========================================================================== */
/* Icon: retry (circular arrow) */
.icon-retry {
width: 13px;
height: 13px;
border: 1.5px solid currentColor;
border-radius: 50%;
border-bottom-color: transparent;
position: relative;
}
.icon-retry::after {
content: "";
position: absolute;
bottom: -1px;
right: -1px;
width: 0;
height: 0;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
border-top: 3px solid currentColor;
transform: rotate(-30deg);
}
/* Icon: edit (pencil) */
.icon-edit {
width: 12px;
height: 12px;
position: relative;
transform: rotate(-45deg);
}
.icon-edit::before {
content: "";
position: absolute;
top: 0;
left: 3px;
width: 6px;
height: 8px;
border: 1.5px solid currentColor;
border-radius: 1px 1px 0 0;
box-sizing: border-box;
}
.icon-edit::after {
content: "";
position: absolute;
bottom: 0;
left: 3px;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid currentColor;
}
/* Icon: rewind (chevrons pointing left) */
.icon-rewind {
width: 14px;
height: 12px;
position: relative;
}
.icon-rewind::before,
.icon-rewind::after {
content: "";
position: absolute;
top: 1px;
width: 6px;
height: 6px;
border-left: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
}
.icon-rewind::before {
left: 1px;
}
.icon-rewind::after {
left: 6px;
}
/* Edit-in-place form */
.msg-edit-form {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.msg-edit-textarea {
width: 100%;
min-height: 40px;
max-height: 200px;
background: var(--bg);
color: var(--fg-bright);
border: 1px solid var(--accent-dim);
border-radius: var(--radius-sm);
padding: 8px 10px;
font-family: var(--font-ui);
font-size: 14px;
line-height: 1.5;
resize: vertical;
outline: none;
transition: border-color 0.12s ease;
box-sizing: border-box;
}
.msg-edit-textarea:focus {
border-color: var(--accent);
}
.msg-edit-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
}
.msg-edit-btn {
padding: 4px 14px;
font-size: 12px;
font-family: var(--font-ui);
font-weight: 500;
border-radius: var(--radius-sm);
cursor: pointer;
border: 1px solid var(--border-strong);
background: var(--bg);
color: var(--fg);
transition:
background 0.1s ease,
border-color 0.1s ease,
color 0.1s ease;
}
.msg-edit-btn:hover {
background: var(--bg-highlight);
}
.msg-edit-btn-send {
background: var(--accent-dim);
color: var(--accent);
border-color: var(--accent);
}
.msg-edit-btn-send:hover {
background: var(--accent);
color: #fff;
}
/* Edit-in-place active state */
.msg-editing {
background: var(--bg-surface);
border-color: var(--accent-dim);
}
.msg-editing .msg-actions {
display: none;
}
/* Busy-state disables action buttons */
[data-busy="true"] .msg-action-btn {
opacity: 0.3;
pointer-events: none;
cursor: not-allowed;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.msg-edit-textarea,
.msg-edit-btn {
transition: none;
}
}
/* ==========================================================================
Per-workstream status bar — pinned above the composer.
Rendered by both the interactive pane (ui/static/app.js) and the
-166
View File
@@ -1197,172 +1197,6 @@ body {
border-radius: 2px;
}
/* ==========================================================================
Action button icons — glyphs rendered inside .msg-action-btn (toolbar
chrome + hover/focus styling lives in the .msg-actions/.msg-action-btn
rules in shared_static/chat.css).
========================================================================== */
/* Icon: retry (circular arrow) */
.icon-retry {
width: 13px;
height: 13px;
border: 1.5px solid currentColor;
border-radius: 50%;
border-bottom-color: transparent;
position: relative;
}
.icon-retry::after {
content: "";
position: absolute;
bottom: -1px;
right: -1px;
width: 0;
height: 0;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
border-top: 3px solid currentColor;
transform: rotate(-30deg);
}
/* Icon: edit (pencil) */
.icon-edit {
width: 12px;
height: 12px;
position: relative;
transform: rotate(-45deg);
}
.icon-edit::before {
content: "";
position: absolute;
top: 0;
left: 3px;
width: 6px;
height: 8px;
border: 1.5px solid currentColor;
border-radius: 1px 1px 0 0;
box-sizing: border-box;
}
.icon-edit::after {
content: "";
position: absolute;
bottom: 0;
left: 3px;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid currentColor;
}
/* Icon: rewind (chevrons pointing left) */
.icon-rewind {
width: 14px;
height: 12px;
position: relative;
}
.icon-rewind::before,
.icon-rewind::after {
content: "";
position: absolute;
top: 1px;
width: 6px;
height: 6px;
border-left: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
}
.icon-rewind::before {
left: 1px;
}
.icon-rewind::after {
left: 6px;
}
/* Edit-in-place form */
.msg-edit-form {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.msg-edit-textarea {
width: 100%;
min-height: 40px;
max-height: 200px;
background: var(--bg);
color: var(--fg-bright);
border: 1px solid var(--accent-dim);
border-radius: var(--radius-sm);
padding: 8px 10px;
font-family: var(--font-ui);
font-size: 14px;
line-height: 1.5;
resize: vertical;
outline: none;
transition: border-color 0.12s ease;
box-sizing: border-box;
}
.msg-edit-textarea:focus {
border-color: var(--accent);
}
.msg-edit-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
}
.msg-edit-btn {
padding: 4px 14px;
font-size: 12px;
font-family: var(--font-ui);
font-weight: 500;
border-radius: var(--radius-sm);
cursor: pointer;
border: 1px solid var(--border-strong);
background: var(--bg);
color: var(--fg);
transition:
background 0.1s ease,
border-color 0.1s ease,
color 0.1s ease;
}
.msg-edit-btn:hover {
background: var(--bg-highlight);
}
.msg-edit-btn-send {
background: var(--accent-dim);
color: var(--accent);
border-color: var(--accent);
}
.msg-edit-btn-send:hover {
background: var(--accent);
color: #fff;
}
/* Edit-in-place active state */
.msg-editing {
background: var(--bg-surface);
border-color: var(--accent-dim);
}
.msg-editing .msg-actions {
display: none;
}
/* Busy-state disables action buttons */
[data-busy="true"] .msg-action-btn {
opacity: 0.3;
pointer-events: none;
cursor: not-allowed;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.msg-edit-textarea,
.msg-edit-btn {
transition: none;
}
}
/* ==========================================================================
Input area — composer DOM + styling lives in /shared/composer.js +
/shared/chat.css. No page-specific overrides needed.