From 06c52e8e7e88f70828f3ddc8aadaaa4e7196e4f4 Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Thu, 28 May 2026 21:18:31 -0700 Subject: [PATCH] refactor(ui): share per-message affordance CSS via chat.css (#549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../static/coordinator/coordinator.css | 173 ------------------ turnstone/shared_static/chat.css | 169 +++++++++++++++++ turnstone/ui/static/style.css | 166 ----------------- 3 files changed, 169 insertions(+), 339 deletions(-) diff --git a/turnstone/console/static/coordinator/coordinator.css b/turnstone/console/static/coordinator/coordinator.css index f7526340..e93ac1b6 100644 --- a/turnstone/console/static/coordinator/coordinator.css +++ b/turnstone/console/static/coordinator/coordinator.css @@ -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; - } -} diff --git a/turnstone/shared_static/chat.css b/turnstone/shared_static/chat.css index 9cc76c5d..6dbec6bf 100644 --- a/turnstone/shared_static/chat.css +++ b/turnstone/shared_static/chat.css @@ -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 diff --git a/turnstone/ui/static/style.css b/turnstone/ui/static/style.css index 42c047b7..383563f8 100644 --- a/turnstone/ui/static/style.css +++ b/turnstone/ui/static/style.css @@ -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.