From 3061e8e4aaf2b67200cc3fa90d8f1da7d108ab59 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Fri, 14 Aug 2026 16:53:16 -0700 Subject: [PATCH 1/3] feat(typography): add a success tone and let Heading forward its props Two gaps that blocked migrating call sites onto the primitives: - No tone mapped to green, so success text had to use raw palette classes. `--success` and `--color-success` already exist in both palettes, so the variant is all that was missing. - `Heading` accepted only `children` and `level` and dropped everything else, while its sibling `Paragraph` forwards rest props to `Text`. Headings that needed a tone, size, weight or className could not use it at all. `Heading` now forwards rest props, with the level-derived size/weight kept as defaults a caller can override. `as` is omitted from the accepted props and `role`/`aria-level` are applied after the spread, so neither the element nor the accessibility attributes can be overridden. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/ui/typography.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/ui/typography.tsx b/src/components/ui/typography.tsx index d5f0bc6a7..c3c478c8f 100644 --- a/src/components/ui/typography.tsx +++ b/src/components/ui/typography.tsx @@ -31,6 +31,7 @@ const textVariants = cva("", { inverted: "text-inverted", info: "text-foreground underline decoration-dotted", warning: "text-warning", + success: "text-success", }, size: { xs: "text-xs", @@ -125,12 +126,17 @@ Paragraph.displayName = "Paragraph"; export const Heading = ({ children, level = 1, -}: PropsWithChildren<{ level: 1 | 2 | 3 | 4 | 5 | 6 }>) => { + size, + weight, + ...rest +}: PropsWithChildren<{ level: 1 | 2 | 3 | 4 | 5 | 6 }> & + Omit) => { return ( From d7aadb7130256699865a658470ab2aae4b685f5d Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Fri, 14 Aug 2026 16:59:58 -0700 Subject: [PATCH 2/3] refactor(ui): map hardcoded text colours onto the tone prop (B18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 19 sites across 13 files: gray-* → subdued, red-* → critical, green-* → success. 7 were raw

/

and become Paragraph/Heading; 12 already used Text and only needed the className swapped for a tone. This also fixes ImportComponent.tsx, where a raw palette colour was paired with dark:text-muted-foreground — the light and dark variants disagreed, and subdued is what the dark half already asked for. Size and weight are preserved per site. The two ImportPipeline headings pass size="md" to keep the base font size a raw

inherited, and keep font-medium via className since there is no matching weight variant. Out of scope, left alone: container border/background palette classes (tone only covers text), lucide icon colours, and Label, which takes no tone. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/shared/ImportPipeline.tsx | 31 ++++++++++++++----- .../components/ImportComponent.tsx | 21 ++++++++----- src/providers/ContextPanelProvider.tsx | 3 +- .../components/ArgumentRow/ArgumentRow.tsx | 2 +- .../components/QuickConnectSubmenu.tsx | 2 +- .../components/BatchArgumentRow.tsx | 6 ++-- .../components/BatchTaskColor.tsx | 2 +- .../Editor/components/CreateSubgraphForm.tsx | 2 +- .../EditorMenuBar/components/RunsMenu.tsx | 2 +- .../components/InitialStateMarker.tsx | 2 +- .../Editor/components/StatComponents.tsx | 2 +- .../nodes/IONode/context/InputDetails.tsx | 2 +- .../nodes/IONode/context/OutputDetails.tsx | 2 +- 13 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/components/shared/ImportPipeline.tsx b/src/components/shared/ImportPipeline.tsx index b04930e22..1eaf6ac37 100644 --- a/src/components/shared/ImportPipeline.tsx +++ b/src/components/shared/ImportPipeline.tsx @@ -18,6 +18,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; +import { Heading, Paragraph } from "@/components/ui/typography"; import { useAnalytics } from "@/providers/AnalyticsProvider"; import { getDefaultEditorPath } from "@/routes/editorRoutes"; import { @@ -226,23 +227,37 @@ const ImportPipeline = ({ {successMessage && (
-

+ Import Successful -

-

+ + {successMessage} -

+
)} {error && (
-

+ Import Failed -

-

+ + {error} -

+
)} diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/ImportComponent.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/ImportComponent.tsx index 87fce0e2f..ba6e62d9f 100644 --- a/src/components/shared/ReactFlow/FlowSidebar/components/ImportComponent.tsx +++ b/src/components/shared/ReactFlow/FlowSidebar/components/ImportComponent.tsx @@ -27,6 +27,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Spinner } from "@/components/ui/spinner"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Paragraph } from "@/components/ui/typography"; import useToastNotification from "@/hooks/useToastNotification"; import { useAnalytics } from "@/providers/AnalyticsProvider"; import { useComponentLibrary } from "@/providers/ComponentLibraryProvider"; @@ -225,12 +226,16 @@ const ImportComponent = ({ {!selectedFileName && (
-

+ Drop your YAML file here or click to browse -

-

+ + Supports .yaml files -

+
)} {selectedFileName && ( @@ -262,9 +267,9 @@ const ImportComponent = ({ )} -

+ Select a YAML file containing a pipeline component -

+ @@ -280,9 +285,9 @@ const ImportComponent = ({ onChange={handleUrlChange} disabled={isPending} /> -

+ Enter the URL of a component YAML file -

+ diff --git a/src/providers/ContextPanelProvider.tsx b/src/providers/ContextPanelProvider.tsx index 313a46458..265f17be5 100644 --- a/src/providers/ContextPanelProvider.tsx +++ b/src/providers/ContextPanelProvider.tsx @@ -8,6 +8,7 @@ import { useState, } from "react"; +import { Paragraph } from "@/components/ui/typography"; import { deselectAllNodes } from "@/utils/flowUtils"; import { @@ -29,7 +30,7 @@ const ContextPanelContext = createRequiredContext( const EMPTY_STATE = (
-

Select an element to see details

+ Select an element to see details
); diff --git a/src/routes/v2/pages/Editor/components/ArgumentRow/ArgumentRow.tsx b/src/routes/v2/pages/Editor/components/ArgumentRow/ArgumentRow.tsx index 8c960438f..46d9788a4 100644 --- a/src/routes/v2/pages/Editor/components/ArgumentRow/ArgumentRow.tsx +++ b/src/routes/v2/pages/Editor/components/ArgumentRow/ArgumentRow.tsx @@ -183,7 +183,7 @@ export const ArgumentRow = observer(function ArgumentRow({ {inputSpec.name.replace(/_/g, " ")} {typeLabel && ( - + ({typeLabel} {!inputSpec.optional ? "*" : ""}) diff --git a/src/routes/v2/pages/Editor/components/ArgumentRow/components/ThunderMenu/components/QuickConnectSubmenu.tsx b/src/routes/v2/pages/Editor/components/ArgumentRow/components/ThunderMenu/components/QuickConnectSubmenu.tsx index 46b68ad95..2a5effd9b 100644 --- a/src/routes/v2/pages/Editor/components/ArgumentRow/components/ThunderMenu/components/QuickConnectSubmenu.tsx +++ b/src/routes/v2/pages/Editor/components/ArgumentRow/components/ThunderMenu/components/QuickConnectSubmenu.tsx @@ -62,7 +62,7 @@ export function QuickConnectSubmenu({ {port.portName} {typeLabel && ( - + {typeLabel} )} diff --git a/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/components/BatchArgumentRow.tsx b/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/components/BatchArgumentRow.tsx index 0e5937952..f026d5424 100644 --- a/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/components/BatchArgumentRow.tsx +++ b/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/components/BatchArgumentRow.tsx @@ -197,17 +197,17 @@ export const BatchArgumentRow = observer(function BatchArgumentRow({ {aggArg.name} {aggArg.typeLabel && ( - + {aggArg.typeLabel} )} {!aggArg.optional && ( - + * )} - + {aggArg.taskIds.length} tasks - + Task color {!allSame && ( diff --git a/src/routes/v2/pages/Editor/components/CreateSubgraphForm.tsx b/src/routes/v2/pages/Editor/components/CreateSubgraphForm.tsx index f1cb9f900..1ff497bd9 100644 --- a/src/routes/v2/pages/Editor/components/CreateSubgraphForm.tsx +++ b/src/routes/v2/pages/Editor/components/CreateSubgraphForm.tsx @@ -39,7 +39,7 @@ export function CreateSubgraphForm({ - + Group {selectedTaskCount} tasks into a reusable component diff --git a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/RunsMenu.tsx b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/RunsMenu.tsx index f62c872c0..8a2bcea35 100644 --- a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/RunsMenu.tsx +++ b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/RunsMenu.tsx @@ -56,7 +56,7 @@ export const RunsMenu = observer(function RunsMenu() { <>
- + {errorCount} validation {errorCount === 1 ? "issue" : "issues"}
diff --git a/src/routes/v2/pages/Editor/components/HistoryContent/components/InitialStateMarker.tsx b/src/routes/v2/pages/Editor/components/HistoryContent/components/InitialStateMarker.tsx index f0a607070..100dc2b01 100644 --- a/src/routes/v2/pages/Editor/components/HistoryContent/components/InitialStateMarker.tsx +++ b/src/routes/v2/pages/Editor/components/HistoryContent/components/InitialStateMarker.tsx @@ -42,7 +42,7 @@ export function InitialStateMarker({ > Initial state {isCurrent && ( - + )} diff --git a/src/routes/v2/pages/Editor/components/StatComponents.tsx b/src/routes/v2/pages/Editor/components/StatComponents.tsx index 45edf3203..5b99f1237 100644 --- a/src/routes/v2/pages/Editor/components/StatComponents.tsx +++ b/src/routes/v2/pages/Editor/components/StatComponents.tsx @@ -11,7 +11,7 @@ interface StatItemProps { export function StatItem({ label, value }: StatItemProps) { return ( - + {label} - + Optional: diff --git a/src/routes/v2/pages/Editor/nodes/IONode/context/OutputDetails.tsx b/src/routes/v2/pages/Editor/nodes/IONode/context/OutputDetails.tsx index 12e3fc378..fdc2470a6 100644 --- a/src/routes/v2/pages/Editor/nodes/IONode/context/OutputDetails.tsx +++ b/src/routes/v2/pages/Editor/nodes/IONode/context/OutputDetails.tsx @@ -65,7 +65,7 @@ export const OutputDetails = observer(function OutputDetails({ > Type - + {String(output.type)} From 154c05bd155901c39c5a2b8cce58f4823df9e368 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Fri, 14 Aug 2026 17:04:34 -0700 Subject: [PATCH 3/3] refactor(ui): move heading sites onto the Heading primitive (B19b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 13 sites across 9 files: 10 `Text as="h*"` and 3 raw

. Every site keeps its exact size, weight, tone and className, so the only rendering change is the role="heading" and aria-level Heading adds. Two of them — PressedKeysList and StatComponents — were already passing role="heading" and aria-level={3} by hand, i.e. reimplementing Heading at the call site. SearchFilter's bare

passes size="md" to keep the base font size it inherited as raw markup. Headings in test files stay raw markup: they are fixtures, not product UI. Co-Authored-By: Claude Opus 5 (1M context) --- .../components/ComponentSpecErrorsList.tsx | 6 ++--- .../FlowSidebar/components/SearchFilter.tsx | 5 +++- .../FlowSidebar/components/SidebarSection.tsx | 8 +++---- src/components/shared/TaskDetails/IO.tsx | 9 ++++++-- .../components/DriverPermissionGate.tsx | 6 ++--- .../Editor/components/EmptyEditorState.tsx | 6 ++--- .../Editor/components/PressedKeysList.tsx | 13 +++-------- .../Editor/components/StatComponents.tsx | 13 +++-------- .../AiChat/components/renderMarkdown.tsx | 23 +++++++++++-------- 9 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/components/shared/ComponentEditor/components/ComponentSpecErrorsList.tsx b/src/components/shared/ComponentEditor/components/ComponentSpecErrorsList.tsx index 30116e7f1..e89c7a262 100644 --- a/src/components/shared/ComponentEditor/components/ComponentSpecErrorsList.tsx +++ b/src/components/shared/ComponentEditor/components/ComponentSpecErrorsList.tsx @@ -1,6 +1,6 @@ import { Icon } from "@/components/ui/icon"; import { BlockStack, InlineStack } from "@/components/ui/layout"; -import { Text } from "@/components/ui/typography"; +import { Heading } from "@/components/ui/typography"; export const ComponentSpecErrorsList = ({ validationErrors, @@ -15,9 +15,9 @@ export const ComponentSpecErrorsList = ({ - + Invalid component spec - +
    {validationErrors.map((error, idx) => ( diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/SearchFilter.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/SearchFilter.tsx index b7da3e7c2..7d15a4091 100644 --- a/src/components/shared/ReactFlow/FlowSidebar/components/SearchFilter.tsx +++ b/src/components/shared/ReactFlow/FlowSidebar/components/SearchFilter.tsx @@ -8,6 +8,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { Heading } from "@/components/ui/typography"; import { type SearchFilterProps } from "@/types/componentLibrary"; import { ComponentSearchFilter } from "@/utils/constants"; @@ -44,7 +45,9 @@ const SearchFilter = ({
    -

    Filter Search

    + + Filter Search +
    {availableFilters.map((filter) => { if (filter === ComponentSearchFilter.EXACTMATCH) return; diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx index f3f329478..7822bfa6d 100644 --- a/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx +++ b/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx @@ -1,7 +1,7 @@ import { type ReactNode } from "react"; import { BlockStack, InlineStack } from "@/components/ui/layout"; -import { Text } from "@/components/ui/typography"; +import { Heading } from "@/components/ui/typography"; import { cn } from "@/lib/utils"; interface SidebarSectionProps { @@ -20,14 +20,14 @@ export const SidebarSection = ({ return ( - {title} - + {headerAction} diff --git a/src/components/shared/TaskDetails/IO.tsx b/src/components/shared/TaskDetails/IO.tsx index ef2956967..d694adffe 100644 --- a/src/components/shared/TaskDetails/IO.tsx +++ b/src/components/shared/TaskDetails/IO.tsx @@ -1,4 +1,5 @@ import { Badge } from "@/components/ui/badge"; +import { Heading } from "@/components/ui/typography"; import type { ComponentSpec } from "@/utils/componentSpec"; interface TaskIOProps { @@ -10,7 +11,9 @@ const TaskIO = ({ componentSpec }: TaskIOProps) => {
    {componentSpec?.inputs && componentSpec.inputs.length > 0 && (
    -

    Inputs

    + + Inputs +
    {componentSpec.inputs.map((input, index) => (
    @@ -48,7 +51,9 @@ const TaskIO = ({ componentSpec }: TaskIOProps) => { {componentSpec?.outputs && componentSpec.outputs.length > 0 && (
    -

    Outputs

    + + Outputs +
    {componentSpec.outputs.map((output) => (
    diff --git a/src/routes/v2/pages/Editor/components/DriverPermissionGate.tsx b/src/routes/v2/pages/Editor/components/DriverPermissionGate.tsx index 56a2af317..f738eca3b 100644 --- a/src/routes/v2/pages/Editor/components/DriverPermissionGate.tsx +++ b/src/routes/v2/pages/Editor/components/DriverPermissionGate.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { Icon } from "@/components/ui/icon"; import { BlockStack } from "@/components/ui/layout"; import { Spinner } from "@/components/ui/spinner"; -import { Text } from "@/components/ui/typography"; +import { Heading, Text } from "@/components/ui/typography"; import type { PipelineFolder } from "@/services/pipelineStorage/PipelineFolder"; import { usePipelineStorage } from "@/services/pipelineStorage/PipelineStorageProvider"; import type { PipelineStorageService } from "@/services/pipelineStorage/PipelineStorageService"; @@ -92,9 +92,9 @@ export function DriverPermissionGate({ return ( - + Permission Required - + This pipeline is stored in “{data.folder?.name}” which requires access permission. diff --git a/src/routes/v2/pages/Editor/components/EmptyEditorState.tsx b/src/routes/v2/pages/Editor/components/EmptyEditorState.tsx index b10cc0061..9598254f5 100644 --- a/src/routes/v2/pages/Editor/components/EmptyEditorState.tsx +++ b/src/routes/v2/pages/Editor/components/EmptyEditorState.tsx @@ -3,7 +3,7 @@ import { useEffect } from "react"; import { Icon } from "@/components/ui/icon"; import { BlockStack, InlineStack } from "@/components/ui/layout"; -import { Text } from "@/components/ui/typography"; +import { Heading } from "@/components/ui/typography"; import { useAnalytics } from "@/providers/AnalyticsProvider"; import { APP_ROUTES } from "@/routes/router"; // TODO: extract PipelineFolders picker to shared or restructure via routing composition @@ -38,9 +38,9 @@ export function EmptyEditorState() { > - + Open Pipeline - + diff --git a/src/routes/v2/pages/Editor/components/PressedKeysList.tsx b/src/routes/v2/pages/Editor/components/PressedKeysList.tsx index 280fa2cf1..a804ad8b5 100644 --- a/src/routes/v2/pages/Editor/components/PressedKeysList.tsx +++ b/src/routes/v2/pages/Editor/components/PressedKeysList.tsx @@ -2,7 +2,7 @@ import { autorun } from "mobx"; import { useEffect, useState } from "react"; import { BlockStack, InlineStack } from "@/components/ui/layout"; -import { Text } from "@/components/ui/typography"; +import { Heading, Text } from "@/components/ui/typography"; import type { KeyConstant } from "@/routes/v2/shared/shortcuts/keys"; import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext"; @@ -17,16 +17,9 @@ export const PressedKeysList = function PressedKeysList() { return ( - + Pressed Keys ({pressedKeys.length}) - + {pressedKeys.map((key) => ( diff --git a/src/routes/v2/pages/Editor/components/StatComponents.tsx b/src/routes/v2/pages/Editor/components/StatComponents.tsx index 5b99f1237..d2c3539e9 100644 --- a/src/routes/v2/pages/Editor/components/StatComponents.tsx +++ b/src/routes/v2/pages/Editor/components/StatComponents.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import { BlockStack, InlineStack } from "@/components/ui/layout"; -import { Text } from "@/components/ui/typography"; +import { Heading, Text } from "@/components/ui/typography"; interface StatItemProps { label: string; @@ -33,16 +33,9 @@ interface StatGroupProps { export function StatGroup({ title, children }: StatGroupProps) { return ( - + {title} - + {children} diff --git a/src/routes/v2/shared/components/AiChat/components/renderMarkdown.tsx b/src/routes/v2/shared/components/AiChat/components/renderMarkdown.tsx index a1a9ea6e1..9a2e98325 100644 --- a/src/routes/v2/shared/components/AiChat/components/renderMarkdown.tsx +++ b/src/routes/v2/shared/components/AiChat/components/renderMarkdown.tsx @@ -10,7 +10,7 @@ import remarkGfm from "remark-gfm"; import { Link } from "@/components/ui/link"; import { Separator } from "@/components/ui/separator"; -import { Paragraph, Text } from "@/components/ui/typography"; +import { Heading, Paragraph } from "@/components/ui/typography"; import { getComponentQueryKey } from "@/hooks/useHydrateComponentReference"; import type { ComponentRefData } from "@/routes/v2/shared/components/AiChat/types"; import { CodeBlock } from "@/routes/v2/shared/components/CodeBlock"; @@ -137,24 +137,29 @@ function MarkdownCode({ const markdownComponents = { h1: ({ children }: { children?: ReactNode }) => ( - + {children} - + ), h2: ({ children }: { children?: ReactNode }) => ( - + {children} - + ), h3: ({ children }: { children?: ReactNode }) => ( - + {children} - + ), h4: ({ children }: { children?: ReactNode }) => ( - + {children} - + ), p: ({ children }: { children?: ReactNode }) => (