feat(FN-2628): merge fusion/fn-2628
This commit is contained in:
110
packages/dashboard/app/components/TaskComments.css
Normal file
110
packages/dashboard/app/components/TaskComments.css
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
.comments-header-row {
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-author-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-actions-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-edit-form {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-edit-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-outcome-text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-compose-form {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-type-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-guidance-hint {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-footer-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-char-count {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-char-count--over {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
background: var(--surface);
|
||||||
|
border: var(--btn-border-width) solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-textarea:focus,
|
||||||
|
.comments-textarea:focus-visible {
|
||||||
|
border-color: var(--todo);
|
||||||
|
box-shadow: var(--focus-ring-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-textarea::placeholder {
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-textarea:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-guidance-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: calc(var(--space-xs) / 4) var(--space-sm);
|
||||||
|
background: color-mix(in srgb, var(--color-info) 15%, transparent);
|
||||||
|
color: var(--color-info);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.comments-textarea {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import type { Task, TaskComment } from "@fusion/core";
|
import type { Task, TaskComment } from "@fusion/core";
|
||||||
import { getErrorMessage } from "@fusion/core";
|
import { getErrorMessage } from "@fusion/core";
|
||||||
|
import "./TaskComments.css";
|
||||||
import { addSteeringComment, updateTaskComment, deleteTaskComment } from "../api";
|
import { addSteeringComment, updateTaskComment, deleteTaskComment } from "../api";
|
||||||
import type { ToastType } from "../hooks/useToast";
|
import type { ToastType } from "../hooks/useToast";
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
|
|||||||
value={editingText}
|
value={editingText}
|
||||||
onChange={(event) => setEditingText(event.target.value)}
|
onChange={(event) => setEditingText(event.target.value)}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="spec-editor-feedback"
|
className="comments-textarea"
|
||||||
/>
|
/>
|
||||||
<div className="comments-edit-actions">
|
<div className="comments-edit-actions">
|
||||||
<button
|
<button
|
||||||
@@ -183,7 +184,7 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="spec-editor-feedback"
|
className="comments-textarea"
|
||||||
/>
|
/>
|
||||||
<div className="comments-footer-row">
|
<div className="comments-footer-row">
|
||||||
<span className={`comments-char-count${isOverLimit ? " comments-char-count--over" : ""}`}>
|
<span className={`comments-char-count${isOverLimit ? " comments-char-count--over" : ""}`}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import { loadAllAppCssBaseOnly } from "../../test/cssFixture";
|
||||||
import { TaskComments } from "../TaskComments";
|
import { TaskComments } from "../TaskComments";
|
||||||
|
|
||||||
vi.mock("../../api", () => ({
|
vi.mock("../../api", () => ({
|
||||||
@@ -215,4 +216,29 @@ describe("TaskComments", () => {
|
|||||||
expect(screen.getByText("Agent guidance")).toBeTruthy();
|
expect(screen.getByText("Agent guidance")).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("CSS styling", () => {
|
||||||
|
const css = loadAllAppCssBaseOnly();
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
selector: "comments-textarea",
|
||||||
|
assertions: [/\.comments-textarea\s*\{[^}]*background:\s*var\(--surface\);/, /\.comments-textarea\s*\{[^}]*border:\s*var\(--btn-border-width\)\s+solid\s+var\(--border\);/],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "ai-guidance-badge",
|
||||||
|
assertions: [/\.ai-guidance-badge\s*\{[^}]*color:\s*var\(--color-info\);/],
|
||||||
|
},
|
||||||
|
])("uses tokenized styles for $selector", ({ assertions }) => {
|
||||||
|
for (const assertion of assertions) {
|
||||||
|
expect(css).toMatch(assertion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not rely on spec-editor-feedback for comment textareas", () => {
|
||||||
|
expect(css).toMatch(/\.comments-textarea\s*\{/);
|
||||||
|
expect(css).not.toContain(".comments-compose-form .spec-editor-feedback");
|
||||||
|
expect(css).not.toContain(".comments-edit-form .spec-editor-feedback");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1418,77 +1418,6 @@ input[type="range"]:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* === Task Comments === */
|
|
||||||
.comments-header-row {
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--space-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-author-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-actions-row {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-edit-form {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
margin-top: var(--space-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-edit-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-outcome-text {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-compose-form {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
margin-top: var(--space-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Comment textareas use --surface so they remain visible against the
|
|
||||||
task detail modal's --card / --bg panels (in light mode --bg and
|
|
||||||
--card are both #ffffff, which made the box blend in). */
|
|
||||||
.comments-compose-form .spec-editor-feedback,
|
|
||||||
.comments-edit-form .spec-editor-feedback {
|
|
||||||
background: var(--surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-type-toggle {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-guidance-hint {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-footer-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-char-count {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments-char-count--over {
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === PR Section === */
|
/* === PR Section === */
|
||||||
.pr-section-icon {
|
.pr-section-icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|||||||
Reference in New Issue
Block a user