feat(FN-3061): align remaining compact icon actions with shared sizing
Fixes FN-3061 aligning compact icon action sizing across Column, PiExtensionsManager, and ScheduleStepsEditor components for consistent UI dimensions. Fusion-Task-Id: FN-3061
This commit is contained in:
@@ -10,8 +10,26 @@ const hiddenDistRoot = join(workspaceRoot, `.tmp-fn-vitest-workspace-resolution-
|
||||
const internalPackages = ["core", "engine", "dashboard"] as const;
|
||||
const movedDistDirs: Array<{ from: string; to: string }> = [];
|
||||
|
||||
function rmSyncWithRetry(path: string) {
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
rmSync(path, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if (code !== "ENOTEMPTY" && code !== "EPERM" && code !== "EEXIST") {
|
||||
throw error;
|
||||
}
|
||||
if (attempt === 4) {
|
||||
throw error;
|
||||
}
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20 * (attempt + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideInternalPackageDistDirs() {
|
||||
rmSync(hiddenDistRoot, { recursive: true, force: true });
|
||||
rmSyncWithRetry(hiddenDistRoot);
|
||||
mkdirSync(hiddenDistRoot, { recursive: true });
|
||||
|
||||
for (const pkg of internalPackages) {
|
||||
@@ -22,7 +40,7 @@ function hideInternalPackageDistDirs() {
|
||||
|
||||
const hiddenPath = join(hiddenDistRoot, `${pkg}-dist`);
|
||||
if (existsSync(hiddenPath)) {
|
||||
rmSync(hiddenPath, { recursive: true, force: true });
|
||||
rmSyncWithRetry(hiddenPath);
|
||||
}
|
||||
renameSync(distPath, hiddenPath);
|
||||
movedDistDirs.push({ from: distPath, to: hiddenPath });
|
||||
@@ -37,13 +55,13 @@ function restoreInternalPackageDistDirs() {
|
||||
}
|
||||
|
||||
if (existsSync(from)) {
|
||||
rmSync(from, { recursive: true, force: true });
|
||||
rmSyncWithRetry(from);
|
||||
}
|
||||
|
||||
renameSync(to, from);
|
||||
}
|
||||
movedDistDirs.length = 0;
|
||||
rmSync(hiddenDistRoot, { recursive: true, force: true });
|
||||
rmSyncWithRetry(hiddenDistRoot);
|
||||
}
|
||||
|
||||
describe("CLI Vitest workspace resolution", () => {
|
||||
|
||||
@@ -614,7 +614,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
||||
{/* Footer with agent ID */}
|
||||
<div className="agent-detail-footer">
|
||||
<button className="btn-icon" onClick={copyAgentId} title="Copy Agent ID">
|
||||
<Copy size={14} />
|
||||
<Copy />
|
||||
</button>
|
||||
<span className="agent-detail-id" onClick={copyAgentId}>
|
||||
{agent.id}
|
||||
|
||||
@@ -144,7 +144,7 @@ export function ChangesDiffModal({
|
||||
title="Previous file (Ctrl+↑)"
|
||||
aria-label="Previous file"
|
||||
>
|
||||
<ChevronLeft size={14} />
|
||||
<ChevronLeft />
|
||||
</button>
|
||||
<span className="changes-nav-indicator" aria-live="polite">
|
||||
{selectedIndex !== null
|
||||
@@ -160,7 +160,7 @@ export function ChangesDiffModal({
|
||||
title="Next file (Ctrl+↓)"
|
||||
aria-label="Next file"
|
||||
>
|
||||
<ChevronRight size={14} />
|
||||
<ChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -309,7 +309,7 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
title="Archive all done tasks"
|
||||
aria-label="Archive all done tasks"
|
||||
>
|
||||
<Archive size={16} />
|
||||
<Archive />
|
||||
</button>
|
||||
)}
|
||||
{isArchived && onToggleCollapse && (
|
||||
@@ -319,6 +319,7 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
title={collapsed ? "Expand archived tasks" : "Collapse archived tasks"}
|
||||
aria-label={collapsed ? "Expand archived tasks" : "Collapse archived tasks"}
|
||||
>
|
||||
{/* Directional chevrons stay explicit for clearer collapsed-state affordance in compact headers. */}
|
||||
{collapsed ? <ChevronDown size={16} /> : <ChevronUp size={16} />}
|
||||
</button>
|
||||
)}
|
||||
@@ -334,7 +335,7 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
title="Column actions"
|
||||
disabled={isMenuBusy}
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
<MoreVertical />
|
||||
</button>
|
||||
{isMenuOpen && (
|
||||
<div className="column-menu-popover" role="menu">
|
||||
|
||||
@@ -552,7 +552,7 @@ export function DevServerView({ addToast, projectId }: DevServerViewProps) {
|
||||
disabled={!effectivePreviewUrl}
|
||||
data-testid="devserver-preview-open-tab"
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
<ExternalLink />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -562,7 +562,7 @@ export function DevServerView({ addToast, projectId }: DevServerViewProps) {
|
||||
disabled={!effectivePreviewUrl}
|
||||
data-testid="devserver-preview-refresh"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
<RefreshCw />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -400,7 +400,7 @@ export function MobileNavBar({
|
||||
<div className="mobile-more-submenu" role="menu" aria-label="Scripts submenu">
|
||||
{scriptsLoading ? (
|
||||
<div className="mobile-more-submenu-loading" data-testid="mobile-more-scripts-loading">
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
<Loader2 className="animate-spin" />
|
||||
<span>Loading scripts…</span>
|
||||
</div>
|
||||
) : scriptEntries.length > 0 ? (
|
||||
@@ -417,7 +417,7 @@ export function MobileNavBar({
|
||||
setIsScriptsSubmenuOpen(false);
|
||||
}}
|
||||
>
|
||||
<Play size={14} />
|
||||
<Play />
|
||||
<span>{name}</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -432,7 +432,7 @@ export function MobileNavBar({
|
||||
onOpenScripts();
|
||||
}}
|
||||
>
|
||||
<FileCode size={14} />
|
||||
<FileCode />
|
||||
<span>Manage Scripts…</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -449,7 +449,7 @@ export function MobileNavBar({
|
||||
onOpenScripts();
|
||||
}}
|
||||
>
|
||||
<FileCode size={14} />
|
||||
<FileCode />
|
||||
<span>No scripts — add one…</span>
|
||||
</button>
|
||||
)
|
||||
@@ -609,7 +609,7 @@ export function MobileNavBar({
|
||||
handleMoreAction(() => onChangeView("dev-server"));
|
||||
}}
|
||||
>
|
||||
<Monitor size={14} />
|
||||
<Monitor />
|
||||
<span>Dev Server</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -621,7 +621,7 @@ export function MobileNavBar({
|
||||
data-testid="mobile-more-item-todos"
|
||||
onClick={() => handleMoreAction(() => onChangeView("todos"))}
|
||||
>
|
||||
<CheckSquare size={14} />
|
||||
<CheckSquare />
|
||||
<span>Todos</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -224,7 +224,7 @@ export function PiExtensionsManager({ addToast, projectId }: PiExtensionsManager
|
||||
title={`Remove ${path}`}
|
||||
aria-label={`Remove ${path}`}
|
||||
>
|
||||
<X size={12} />
|
||||
<X />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
@@ -421,7 +421,7 @@ export function PiExtensionsManager({ addToast, projectId }: PiExtensionsManager
|
||||
disabled={extensionsLoading}
|
||||
title="Refresh extensions"
|
||||
>
|
||||
<RefreshCw size={14} className={extensionsLoading ? "spin" : ""} />
|
||||
<RefreshCw className={extensionsLoading ? "spin" : ""} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="pi-ext-description">
|
||||
|
||||
@@ -641,9 +641,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
padding-right: var(--space-xs);
|
||||
}
|
||||
|
||||
.planning-option {
|
||||
|
||||
@@ -190,7 +190,7 @@ export function ProjectDetectionResults({
|
||||
title="Edit name"
|
||||
disabled={isRegistering}
|
||||
>
|
||||
<Edit2 size={12} />
|
||||
<Edit2 />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -202,7 +202,7 @@ export function RoutineCard({ routine, onEdit, onDelete, onRun, onToggle, runnin
|
||||
title={running ? "Running…" : "Run now"}
|
||||
aria-label={running ? "Running…" : `Run ${routine.name} now`}
|
||||
>
|
||||
{running ? <Loader2 size={14} className="spinner" /> : <Play size={14} />}
|
||||
{running ? <Loader2 className="spinner" /> : <Play />}
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -211,7 +211,7 @@ export function RoutineCard({ routine, onEdit, onDelete, onRun, onToggle, runnin
|
||||
aria-label={routine.enabled ? `Disable ${routine.name}` : `Enable ${routine.name}`}
|
||||
aria-pressed={routine.enabled}
|
||||
>
|
||||
{routine.enabled ? <Pause size={14} /> : <Play size={14} />}
|
||||
{routine.enabled ? <Pause /> : <Play />}
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -219,7 +219,7 @@ export function RoutineCard({ routine, onEdit, onDelete, onRun, onToggle, runnin
|
||||
title="Edit"
|
||||
aria-label={`Edit ${routine.name}`}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
<Pencil />
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -227,7 +227,7 @@ export function RoutineCard({ routine, onEdit, onDelete, onRun, onToggle, runnin
|
||||
title="Delete"
|
||||
aria-label={`Delete ${routine.name}`}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +194,7 @@ export function ScheduleCard({ schedule, onEdit, onDelete, onRun, onToggle, runn
|
||||
title={running ? "Running…" : "Run now"}
|
||||
aria-label={running ? "Running…" : `Run ${schedule.name} now`}
|
||||
>
|
||||
<Play size={14} />
|
||||
<Play />
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -203,7 +203,7 @@ export function ScheduleCard({ schedule, onEdit, onDelete, onRun, onToggle, runn
|
||||
aria-label={schedule.enabled ? `Disable ${schedule.name}` : `Enable ${schedule.name}`}
|
||||
aria-pressed={schedule.enabled}
|
||||
>
|
||||
{schedule.enabled ? <Pause size={14} /> : <Play size={14} />}
|
||||
{schedule.enabled ? <Pause /> : <Play />}
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -211,7 +211,7 @@ export function ScheduleCard({ schedule, onEdit, onDelete, onRun, onToggle, runn
|
||||
title="Edit"
|
||||
aria-label={`Edit ${schedule.name}`}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
<Pencil />
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon"
|
||||
@@ -219,7 +219,7 @@ export function ScheduleCard({ schedule, onEdit, onDelete, onRun, onToggle, runn
|
||||
title="Delete"
|
||||
aria-label={`Delete ${schedule.name}`}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -421,7 +421,7 @@ export function ScheduleStepsEditor({ steps, onChange, onEditingChange }: Schedu
|
||||
title="Move up"
|
||||
aria-label={`Move ${step.name} up`}
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
<ChevronUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -431,7 +431,7 @@ export function ScheduleStepsEditor({ steps, onChange, onEditingChange }: Schedu
|
||||
title="Move down"
|
||||
aria-label={`Move ${step.name} down`}
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
<ChevronDown />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -440,7 +440,7 @@ export function ScheduleStepsEditor({ steps, onChange, onEditingChange }: Schedu
|
||||
title="Edit"
|
||||
aria-label={`Edit ${step.name}`}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
<Pencil />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -449,7 +449,7 @@ export function ScheduleStepsEditor({ steps, onChange, onEditingChange }: Schedu
|
||||
title="Delete"
|
||||
aria-label={`Delete ${step.name}`}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -715,7 +715,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
title="Move up"
|
||||
aria-label="Move subtask up"
|
||||
>
|
||||
<ArrowUp size={14} />
|
||||
<ArrowUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -725,7 +725,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
title="Move down"
|
||||
aria-label="Move subtask down"
|
||||
>
|
||||
<ArrowDown size={14} />
|
||||
<ArrowDown />
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm" onClick={() => removeSubtask(subtask.id)} disabled={view.type === "creating"}>
|
||||
<Trash2 size={14} /> Remove
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
|
||||
.system-stats-modal__header-actions .btn-icon svg {
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
width: var(--btn-icon-size);
|
||||
height: var(--btn-icon-size);
|
||||
}
|
||||
|
||||
.system-stats-modal__refresh--spinning {
|
||||
|
||||
@@ -325,7 +325,7 @@ export function TaskChangesTab({ taskId, worktree, projectId, column, mergeDetai
|
||||
title="Previous file"
|
||||
aria-label="Previous file"
|
||||
>
|
||||
<ChevronLeft size={14} />
|
||||
<ChevronLeft />
|
||||
</button>
|
||||
<span className="changes-nav-indicator" aria-live="polite">
|
||||
{currentFileIndex !== null ? `${currentFileIndex + 1}/${files.length}` : `—/${files.length}`}
|
||||
@@ -337,7 +337,7 @@ export function TaskChangesTab({ taskId, worktree, projectId, column, mergeDetai
|
||||
title="Next file"
|
||||
aria-label="Next file"
|
||||
>
|
||||
<ChevronRight size={14} />
|
||||
<ChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -364,7 +364,7 @@ export function TaskChangesTab({ taskId, worktree, projectId, column, mergeDetai
|
||||
title="Expand to full-screen diff view"
|
||||
aria-label="Expand diff view"
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
<Maximize2 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1168,7 +1168,7 @@ export function TaskForm({
|
||||
data-testid={`workflow-step-move-up-${stepId}`}
|
||||
title="Move up"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
<ChevronUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1178,7 +1178,7 @@ export function TaskForm({
|
||||
data-testid={`workflow-step-move-down-${stepId}`}
|
||||
title="Move down"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
<ChevronDown />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1188,7 +1188,7 @@ export function TaskForm({
|
||||
data-testid={`workflow-step-remove-${stepId}`}
|
||||
title="Remove"
|
||||
>
|
||||
<X size={14} />
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -324,7 +324,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label="Add list"
|
||||
data-testid="add-list-button"
|
||||
>
|
||||
<Plus size={14} />
|
||||
<Plus />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -355,7 +355,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
}}
|
||||
aria-label="Save list"
|
||||
>
|
||||
<Check size={14} />
|
||||
<Check />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -366,7 +366,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
}}
|
||||
aria-label="Cancel list"
|
||||
>
|
||||
<X size={14} />
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -423,7 +423,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
}}
|
||||
aria-label="Save list rename"
|
||||
>
|
||||
<Check size={14} />
|
||||
<Check />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -431,7 +431,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
onClick={handleCancelRenameList}
|
||||
aria-label="Cancel list rename"
|
||||
>
|
||||
<X size={14} />
|
||||
<X />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
@@ -456,7 +456,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Rename ${list.title}`}
|
||||
data-testid={`rename-list-button-${list.id}`}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
<Pencil />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -467,7 +467,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Delete ${list.title}`}
|
||||
data-testid={`delete-list-button-${list.id}`}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -586,7 +586,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
}}
|
||||
aria-label="Save item edit"
|
||||
>
|
||||
<Check size={14} />
|
||||
<Check />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -594,7 +594,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
onClick={handleCancelEditItem}
|
||||
aria-label="Cancel item edit"
|
||||
>
|
||||
<X size={14} />
|
||||
<X />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
@@ -610,7 +610,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Move ${item.text} up`}
|
||||
data-testid={`move-up-${item.id}`}
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
<ChevronUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -622,7 +622,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Move ${item.text} down`}
|
||||
data-testid={`move-down-${item.id}`}
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
<ChevronDown />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -634,7 +634,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Create task from ${item.text}`}
|
||||
data-testid={`create-task-from-${item.id}`}
|
||||
>
|
||||
<PlusCircle size={14} />
|
||||
<PlusCircle />
|
||||
</button>
|
||||
<div
|
||||
className="todo-agent-picker-trigger"
|
||||
@@ -650,7 +650,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Assign ${item.text} to agent`}
|
||||
data-testid={`assign-agent-for-${item.id}`}
|
||||
>
|
||||
<Bot size={14} />
|
||||
<Bot />
|
||||
</button>
|
||||
{showAgentPicker && activeItemForAgent === item.id && (
|
||||
<div
|
||||
@@ -673,7 +673,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
void handleCreateTaskAndAssign(item, agent.id);
|
||||
}}
|
||||
>
|
||||
<Bot size={14} />
|
||||
<Bot />
|
||||
<span>{agent.name}</span>
|
||||
<span className="todo-agent-picker-role">{agent.role}</span>
|
||||
</button>
|
||||
@@ -691,7 +691,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Edit ${item.text}`}
|
||||
data-testid={`edit-item-${item.id}`}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
<Pencil />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -702,7 +702,7 @@ export function TodoView({ projectId, addToast }: TodoViewProps) {
|
||||
aria-label={`Delete ${item.text}`}
|
||||
data-testid={`delete-item-${item.id}`}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Trash2 />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -391,7 +391,7 @@ export function WorkflowResultsTab({
|
||||
data-testid={`workflow-step-move-up-${stepId}`}
|
||||
title="Move up"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
<ChevronUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -401,7 +401,7 @@ export function WorkflowResultsTab({
|
||||
data-testid={`workflow-step-move-down-${stepId}`}
|
||||
title="Move down"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
<ChevronDown />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -410,7 +410,7 @@ export function WorkflowResultsTab({
|
||||
data-testid={`workflow-step-remove-${stepId}`}
|
||||
title="Remove"
|
||||
>
|
||||
<X size={14} />
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +55,23 @@ describe("mobile CSS foundation", () => {
|
||||
expect(touchTargetMatch![1]).toContain("min-height: 44px;");
|
||||
});
|
||||
|
||||
it("defines the shared btn-icon size variable contract", () => {
|
||||
const css = loadAllAppCss();
|
||||
|
||||
const btnIconBlock = css.match(/\.btn-icon\s*\{([\s\S]*?)\}/);
|
||||
expect(btnIconBlock).toBeTruthy();
|
||||
expect(btnIconBlock![1]).toContain("--btn-icon-size: var(--icon-size-md);");
|
||||
|
||||
const btnIconSvgBlock = css.match(/\.btn-icon\s*>\s*svg\s*\{([\s\S]*?)\}/);
|
||||
expect(btnIconSvgBlock).toBeTruthy();
|
||||
expect(btnIconSvgBlock![1]).toContain("width: var(--btn-icon-size);");
|
||||
expect(btnIconSvgBlock![1]).toContain("height: var(--btn-icon-size);");
|
||||
|
||||
const btnIconCompactBlock = css.match(/\.btn-icon\.btn-sm[\s\S]*?\{([\s\S]*?)\}/);
|
||||
expect(btnIconCompactBlock).toBeTruthy();
|
||||
expect(btnIconCompactBlock![1]).toContain("--btn-icon-size: var(--icon-size-sm);");
|
||||
});
|
||||
|
||||
it("enforces 16px font size for text inputs in the main mobile media query", () => {
|
||||
const css = loadAllAppCss();
|
||||
const mobileSection = getMainMobileSection(css);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RootErrorBoundary } from "./components/ErrorBoundary";
|
||||
import { App } from "./App";
|
||||
import { installAuthFetch } from "./auth";
|
||||
import { installVersionCheck } from "./versionCheck";
|
||||
import { installSwUpdate } from "./swUpdate";
|
||||
import "./styles.css";
|
||||
|
||||
// Install the bearer-token fetch wrapper before React mounts so every API
|
||||
@@ -21,13 +22,4 @@ createRoot(document.getElementById("root")!).render(
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
if (import.meta.env.PROD && "serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js")
|
||||
.then((registration) => {
|
||||
console.log("SW registered:", registration.scope);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("SW registration failed:", error);
|
||||
});
|
||||
}
|
||||
installSwUpdate();
|
||||
|
||||
@@ -8,6 +8,12 @@ const APP_SHELL_URLS = [
|
||||
"/icons/icon-512.png",
|
||||
];
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil((async () => {
|
||||
try {
|
||||
|
||||
@@ -136,6 +136,8 @@ html {
|
||||
--header-padding: var(--space-md) var(--space-xl);
|
||||
--column-gap: var(--space-md);
|
||||
--board-padding: var(--space-lg) var(--space-xl);
|
||||
--icon-size-md: 16px;
|
||||
--icon-size-sm: 14px;
|
||||
|
||||
/* Layout tokens for fixed footer (ExecutorStatusBar) */
|
||||
--header-height: 57px;
|
||||
@@ -704,6 +706,24 @@ body {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
--btn-icon-size: var(--icon-size-md);
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.btn-icon > svg {
|
||||
width: var(--btn-icon-size);
|
||||
height: var(--btn-icon-size);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn.btn-icon.btn-sm,
|
||||
.btn.btn-icon.btn--sm,
|
||||
.btn-icon.btn-sm,
|
||||
.btn-icon.btn--sm {
|
||||
--btn-icon-size: var(--icon-size-sm);
|
||||
}
|
||||
|
||||
/* === Button Modifiers (BEM style) === */
|
||||
.btn--sm {
|
||||
padding: 4px 8px;
|
||||
|
||||
46
packages/dashboard/app/swUpdate.ts
Normal file
46
packages/dashboard/app/swUpdate.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { reloadOnce } from "./versionCheck";
|
||||
|
||||
function promptUpdate(worker: ServiceWorker): void {
|
||||
worker.postMessage({ type: "SKIP_WAITING" });
|
||||
}
|
||||
|
||||
function watchInstalling(installing: ServiceWorker): void {
|
||||
installing.addEventListener("statechange", () => {
|
||||
if (installing.state === "installed" && navigator.serviceWorker.controller) {
|
||||
promptUpdate(installing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function installSwUpdate(): void {
|
||||
if (!import.meta.env.PROD || !("serviceWorker" in navigator)) return;
|
||||
|
||||
let reloading = false;
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
if (reloading) return;
|
||||
reloading = true;
|
||||
reloadOnce("service worker activated new version");
|
||||
});
|
||||
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js")
|
||||
.then((registration) => {
|
||||
console.log("SW registered:", registration.scope);
|
||||
|
||||
if (registration.waiting && navigator.serviceWorker.controller) {
|
||||
promptUpdate(registration.waiting);
|
||||
}
|
||||
|
||||
if (registration.installing) {
|
||||
watchInstalling(registration.installing);
|
||||
}
|
||||
|
||||
registration.addEventListener("updatefound", () => {
|
||||
const installing = registration.installing;
|
||||
if (installing) watchInstalling(installing);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("SW registration failed:", error);
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export function consumeVersionUpdateFlag(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function reloadOnce(reason: string): void {
|
||||
export function reloadOnce(reason: string): void {
|
||||
if (sessionStorage.getItem(RELOAD_FLAG)) {
|
||||
console.warn("[versionCheck] reload already attempted, suppressing", reason);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user