Spaces:
Running
OLED glow polish + ⌘K keyboard shortcut from design system
Browse filesCSS — Dark Mode (OLED) checklist items (ui-ux-pro-max-skill):
- color-scheme: dark on :root (browser chrome adapts: scrollbars, inputs)
- Streaming cursor: green + box-shadow glow (0 0 6px rgba(34,197,94,0.7))
- User bubble: dark-green→green gradient + shadow (replaces flat accent)
- Btn primary: glow on rest (0.25 alpha), stronger on hover (0.35 alpha)
- Active repo item: inset + outer glow rings
- Input focus: green glow ring (rgba(34,197,94,0.12))
- Agent trace live: glow border
- Onboarding active step: ambient glow
UX — Productivity Tool must_have: keyboard-shortcuts:
- ⌘K / Ctrl+K global shortcut focuses chat input from anywhere
- Switches from Graph view to Chat if needed before focusing
- ⌘K hint badge shown in input bar when idle (hides during streaming/typing)
Send button: shortened "Run Agent" → "Run" to match button width
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ui/src/App.jsx +21 -1
- ui/src/index.css +47 -7
|
@@ -24,6 +24,21 @@ export default function App() {
|
|
| 24 |
const textareaRef = useRef(null);
|
| 25 |
const stopStream = useRef(null); // cleanup fn for active SSE
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
// Auto-grow textarea as user types
|
| 28 |
useEffect(() => {
|
| 29 |
const el = textareaRef.current;
|
|
@@ -421,10 +436,15 @@ export default function App() {
|
|
| 421 |
className="btn"
|
| 422 |
onClick={handleSubmit}
|
| 423 |
disabled={!input.trim() || streaming}
|
|
|
|
| 424 |
>
|
| 425 |
-
{streaming ? <span className="spinner" /> : agentMode ? "Run
|
| 426 |
</button>
|
| 427 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
</div>
|
| 429 |
</>
|
| 430 |
)}
|
|
|
|
| 24 |
const textareaRef = useRef(null);
|
| 25 |
const stopStream = useRef(null); // cleanup fn for active SSE
|
| 26 |
|
| 27 |
+
// ⌘K / Ctrl+K — focus the input from anywhere in the app.
|
| 28 |
+
// Productivity Tool must_have: keyboard-shortcuts (ui-ux-pro-max-skill #16).
|
| 29 |
+
useEffect(() => {
|
| 30 |
+
function onGlobalKey(e) {
|
| 31 |
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
| 32 |
+
e.preventDefault();
|
| 33 |
+
if (view === "graph") setView("chat");
|
| 34 |
+
// Small delay if we just switched views (textarea may not be mounted yet)
|
| 35 |
+
setTimeout(() => textareaRef.current?.focus(), 20);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
window.addEventListener("keydown", onGlobalKey);
|
| 39 |
+
return () => window.removeEventListener("keydown", onGlobalKey);
|
| 40 |
+
}, [view]);
|
| 41 |
+
|
| 42 |
// Auto-grow textarea as user types
|
| 43 |
useEffect(() => {
|
| 44 |
const el = textareaRef.current;
|
|
|
|
| 436 |
className="btn"
|
| 437 |
onClick={handleSubmit}
|
| 438 |
disabled={!input.trim() || streaming}
|
| 439 |
+
aria-label={streaming ? "Generating…" : agentMode ? "Run Agent" : "Ask"}
|
| 440 |
>
|
| 441 |
+
{streaming ? <span className="spinner" aria-hidden="true" /> : agentMode ? "Run" : "Ask"}
|
| 442 |
</button>
|
| 443 |
</div>
|
| 444 |
+
{/* ⌘K hint — shown when idle, guides users to the keyboard shortcut */}
|
| 445 |
+
{!streaming && !input && (
|
| 446 |
+
<div className="input-hint" aria-hidden="true">⌘K</div>
|
| 447 |
+
)}
|
| 448 |
</div>
|
| 449 |
</>
|
| 450 |
)}
|
|
@@ -68,6 +68,10 @@
|
|
| 68 |
--shadow-lg: 0 8px 32px rgba(0,0,0,0.6);
|
| 69 |
}
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
body {
|
| 72 |
font-family: var(--sans);
|
| 73 |
background: var(--bg);
|
|
@@ -196,11 +200,16 @@ textarea:focus-visible {
|
|
| 196 |
font-size: 13px;
|
| 197 |
font-weight: 600;
|
| 198 |
padding: 8px 12px;
|
| 199 |
-
transition: opacity var(--transition), transform var(--transition);
|
|
|
|
|
|
|
| 200 |
}
|
| 201 |
|
| 202 |
-
.btn:hover {
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
| 204 |
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 205 |
.btn.secondary {
|
| 206 |
background: var(--pill-bg);
|
|
@@ -232,6 +241,8 @@ textarea:focus-visible {
|
|
| 232 |
.repo-item.active {
|
| 233 |
background: var(--accent-dim);
|
| 234 |
border-color: var(--accent);
|
|
|
|
|
|
|
| 235 |
}
|
| 236 |
|
| 237 |
.repo-slug {
|
|
@@ -397,9 +408,12 @@ textarea:focus-visible {
|
|
| 397 |
}
|
| 398 |
|
| 399 |
.message.user .bubble {
|
| 400 |
-
|
|
|
|
|
|
|
| 401 |
color: #000;
|
| 402 |
border-bottom-right-radius: 3px;
|
|
|
|
| 403 |
}
|
| 404 |
|
| 405 |
.message.assistant .bubble {
|
|
@@ -596,7 +610,10 @@ textarea:focus-visible {
|
|
| 596 |
transition: border-color var(--transition);
|
| 597 |
}
|
| 598 |
|
| 599 |
-
.input-bar textarea:focus {
|
|
|
|
|
|
|
|
|
|
| 600 |
.input-bar textarea::placeholder { color: var(--muted); }
|
| 601 |
|
| 602 |
/* ── Empty state ─────────────────────────────────────────────── */
|
|
@@ -636,7 +653,7 @@ textarea:focus-visible {
|
|
| 636 |
|
| 637 |
.onboarding-step.active {
|
| 638 |
border-color: var(--accent);
|
| 639 |
-
box-shadow: 0 0 0 1px
|
| 640 |
}
|
| 641 |
|
| 642 |
.step-num {
|
|
@@ -758,10 +775,12 @@ textarea:focus-visible {
|
|
| 758 |
.cursor {
|
| 759 |
display: inline-block;
|
| 760 |
width: 2px; height: 14px;
|
| 761 |
-
background: var(--
|
| 762 |
margin-left: 2px;
|
| 763 |
vertical-align: text-bottom;
|
| 764 |
animation: blink 1s step-end infinite;
|
|
|
|
|
|
|
| 765 |
}
|
| 766 |
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
| 767 |
|
|
@@ -940,6 +959,7 @@ textarea:focus-visible {
|
|
| 940 |
border-radius: var(--radius-md);
|
| 941 |
padding: 8px 12px;
|
| 942 |
background: var(--accent-dim);
|
|
|
|
| 943 |
}
|
| 944 |
|
| 945 |
.agent-trace-label {
|
|
@@ -1096,6 +1116,26 @@ textarea:focus-visible {
|
|
| 1096 |
50% { opacity: 0.3; }
|
| 1097 |
}
|
| 1098 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
/* ── Scrollbar ───────────────────────────────────────────────── */
|
| 1100 |
::-webkit-scrollbar { width: 6px; }
|
| 1101 |
::-webkit-scrollbar-track { background: transparent; }
|
|
|
|
| 68 |
--shadow-lg: 0 8px 32px rgba(0,0,0,0.6);
|
| 69 |
}
|
| 70 |
|
| 71 |
+
/* color-scheme: dark — browser chrome (scrollbars, inputs, select) adapts.
|
| 72 |
+
Required by Dark Mode (OLED) checklist in ui-ux-pro-max-skill. */
|
| 73 |
+
:root { color-scheme: dark; }
|
| 74 |
+
|
| 75 |
body {
|
| 76 |
font-family: var(--sans);
|
| 77 |
background: var(--bg);
|
|
|
|
| 200 |
font-size: 13px;
|
| 201 |
font-weight: 600;
|
| 202 |
padding: 8px 12px;
|
| 203 |
+
transition: opacity var(--transition), transform var(--transition), box-shadow var(--transition);
|
| 204 |
+
/* Minimal OLED glow on primary CTA */
|
| 205 |
+
box-shadow: 0 2px 8px rgba(34,197,94,0.25);
|
| 206 |
}
|
| 207 |
|
| 208 |
+
.btn:hover {
|
| 209 |
+
opacity: 0.9;
|
| 210 |
+
box-shadow: 0 4px 16px rgba(34,197,94,0.35);
|
| 211 |
+
}
|
| 212 |
+
.btn:active { transform: scale(0.97); box-shadow: none; }
|
| 213 |
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 214 |
.btn.secondary {
|
| 215 |
background: var(--pill-bg);
|
|
|
|
| 241 |
.repo-item.active {
|
| 242 |
background: var(--accent-dim);
|
| 243 |
border-color: var(--accent);
|
| 244 |
+
/* Minimal glow — OLED dark mode accent highlight (ui-ux-pro-max-skill) */
|
| 245 |
+
box-shadow: 0 0 0 1px rgba(34,197,94,0.12), inset 0 0 8px rgba(34,197,94,0.05);
|
| 246 |
}
|
| 247 |
|
| 248 |
.repo-slug {
|
|
|
|
| 408 |
}
|
| 409 |
|
| 410 |
.message.user .bubble {
|
| 411 |
+
/* Gradient instead of flat green — premium OLED look.
|
| 412 |
+
Dark-green → bright-green matches the "run" semantic. */
|
| 413 |
+
background: linear-gradient(135deg, #166534 0%, #22C55E 100%);
|
| 414 |
color: #000;
|
| 415 |
border-bottom-right-radius: 3px;
|
| 416 |
+
box-shadow: 0 2px 12px rgba(34,197,94,0.2);
|
| 417 |
}
|
| 418 |
|
| 419 |
.message.assistant .bubble {
|
|
|
|
| 610 |
transition: border-color var(--transition);
|
| 611 |
}
|
| 612 |
|
| 613 |
+
.input-bar textarea:focus {
|
| 614 |
+
border-color: var(--accent);
|
| 615 |
+
box-shadow: 0 0 0 3px rgba(34,197,94,0.12);
|
| 616 |
+
}
|
| 617 |
.input-bar textarea::placeholder { color: var(--muted); }
|
| 618 |
|
| 619 |
/* ── Empty state ─────────────────────────────────────────────── */
|
|
|
|
| 653 |
|
| 654 |
.onboarding-step.active {
|
| 655 |
border-color: var(--accent);
|
| 656 |
+
box-shadow: 0 0 0 1px rgba(34,197,94,0.15), 0 0 12px rgba(34,197,94,0.08), var(--shadow-sm);
|
| 657 |
}
|
| 658 |
|
| 659 |
.step-num {
|
|
|
|
| 775 |
.cursor {
|
| 776 |
display: inline-block;
|
| 777 |
width: 2px; height: 14px;
|
| 778 |
+
background: var(--accent);
|
| 779 |
margin-left: 2px;
|
| 780 |
vertical-align: text-bottom;
|
| 781 |
animation: blink 1s step-end infinite;
|
| 782 |
+
/* Accent glow on cursor — visible "thinking" indicator */
|
| 783 |
+
box-shadow: 0 0 6px rgba(34,197,94,0.7);
|
| 784 |
}
|
| 785 |
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
| 786 |
|
|
|
|
| 959 |
border-radius: var(--radius-md);
|
| 960 |
padding: 8px 12px;
|
| 961 |
background: var(--accent-dim);
|
| 962 |
+
box-shadow: 0 0 0 1px rgba(34,197,94,0.1);
|
| 963 |
}
|
| 964 |
|
| 965 |
.agent-trace-label {
|
|
|
|
| 1116 |
50% { opacity: 0.3; }
|
| 1117 |
}
|
| 1118 |
|
| 1119 |
+
/* ── Keyboard shortcut hint ──────────────────────────────────── */
|
| 1120 |
+
/* Shown inside the input bar when textarea is empty and not streaming.
|
| 1121 |
+
Follows the Productivity Tool must_have: keyboard-shortcuts from
|
| 1122 |
+
ui-ux-pro-max-skill. Inspired by Linear/Raycast/Vercel AI patterns. */
|
| 1123 |
+
.input-hint {
|
| 1124 |
+
position: absolute;
|
| 1125 |
+
right: 72px;
|
| 1126 |
+
bottom: 19px;
|
| 1127 |
+
font-size: 11px;
|
| 1128 |
+
color: var(--faint);
|
| 1129 |
+
font-family: var(--mono);
|
| 1130 |
+
pointer-events: none;
|
| 1131 |
+
background: var(--surface);
|
| 1132 |
+
border: 1px solid var(--border-subtle);
|
| 1133 |
+
padding: 1px 6px;
|
| 1134 |
+
border-radius: var(--radius-sm);
|
| 1135 |
+
letter-spacing: 0.02em;
|
| 1136 |
+
user-select: none;
|
| 1137 |
+
}
|
| 1138 |
+
|
| 1139 |
/* ── Scrollbar ───────────────────────────────────────────────── */
|
| 1140 |
::-webkit-scrollbar { width: 6px; }
|
| 1141 |
::-webkit-scrollbar-track { background: transparent; }
|