diff --git a/docs/DESIGN-SYSTEM.md b/docs/DESIGN-SYSTEM.md index dd6ec8554..11fe829ec 100644 --- a/docs/DESIGN-SYSTEM.md +++ b/docs/DESIGN-SYSTEM.md @@ -33,8 +33,8 @@ Hierarchy is conveyed through weight and color, not size. Most labels, titles, a Weight has three tiers, applied by role: - **Screen titles** — the title at the top of a screen — use `` (`packages/app/src/components/headers/screen-title.tsx:31-34`), which renders `fontSize.base` at weight `400` on compact and `300` on desktop. Top-of-screen titles are lighter on desktop, not heavier. The workspace screen header follows the same rule (`packages/app/src/screens/workspace/workspace-screen.tsx:3052-3057`). -- **Structural labels** use `fontWeight.medium`. This applies to section labels above a stack of rows (`packages/app/src/components/agent-list.tsx:519-523`, `packages/app/src/components/keyboard-shortcuts-dialog.tsx:63-67`), form field labels above an input inside a modal (`packages/app/src/components/add-host-modal.tsx:19-23`, `packages/app/src/components/pair-link-modal.tsx:24-28`), the title at the top of a modal/sheet/dialog (`packages/app/src/components/adaptive-modal-sheet.tsx:90-94`, `packages/app/src/components/ui/combobox.tsx:1607-1611`, `packages/app/src/components/welcome-screen.tsx:48-53`), action button labels in tight components such as the callout actions (`packages/app/src/components/callout-card.tsx:218-221`), and inline data emphasis on dense metadata rows (`packages/app/src/components/git-diff-pane.tsx:2322-2327`, `packages/app/src/components/file-explorer-pane.tsx:1115-1122`). -- **Content** uses `fontWeight.normal`. This applies to settings rows (`packages/app/src/styles/settings.ts`), sidebar primary list-item titles (`packages/app/src/components/sidebar-workspace-list.tsx:2680-2686`, `packages/app/src/components/agent-list.tsx:572-578`), ` + ); } +function resolveReadFailureCopy(input: { + kind: ReadFailureCalloutProps["kind"]; + error: unknown; + hasMultipleHosts: boolean; +}): { testID: string; title: string; description: string } { + if (input.kind === "invalid_project_config") { + return { + testID: "invalid-callout", + title: "paseo.json couldn't be parsed", + description: "Fix the file on disk, then reload.", + }; + } + if (input.kind === "project_not_found") { + return { + testID: "project-not-found-callout", + title: "This host doesn't have this project", + description: input.hasMultipleHosts + ? "Switch to another host above, or reload." + : "The selected host has no record of this project.", + }; + } + if (input.kind === "transport") { + const detail = errorToDetail(input.error); + return { + testID: "read-transport-callout", + title: "Couldn't load paseo.json", + description: detail ?? "The host didn't respond.", + }; + } + return { + testID: "read-failed-callout", + title: "Couldn't load paseo.json", + description: "Reload to try again.", + }; +} + +function errorToDetail(error: unknown): string | null { + if (error instanceof Error && error.message.length > 0) return error.message; + if (typeof error === "string" && error.length > 0) return error; + return null; +} + interface ProjectConfigFormProps { baseConfig: PaseoConfigRaw; revision: PaseoConfigRevision | null; @@ -508,18 +497,6 @@ function ProjectConfigForm({ [draft.scripts], ); - const staleActions = useMemo( - () => [{ label: "Reload", onPress: handleReload, variant: "primary" }], - [handleReload], - ); - const writeFailedActions = useMemo( - () => [ - { label: "Try again", onPress: handleSave, variant: "primary" }, - { label: "Reload", onPress: handleReload, variant: "secondary" }, - ], - [handleSave, handleReload], - ); - const scriptsTrailing = useMemo( () => ( - + > + + ) : null} {isWriteFailed ? ( - + > + + + ) : null} @@ -1033,8 +1034,7 @@ const styles = StyleSheet.create((theme) => ({ padding: theme.spacing[6], }, errorBlock: { - padding: theme.spacing[4], - gap: theme.spacing[3], + marginTop: theme.spacing[2], }, lifecycleInput: { color: theme.colors.foreground, diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts index d597d3cd5..4b20000f5 100644 --- a/packages/app/src/styles/theme.ts +++ b/packages/app/src/styles/theme.ts @@ -164,7 +164,7 @@ const lightSemanticColors = { accentForeground: "#ffffff", // Semantic - destructive: "#dc2626", + destructive: "#b04138", // dark warm red on white — calm but unambiguously red destructiveForeground: "#ffffff", success: "#20744A", successForeground: "#ffffff", @@ -233,6 +233,7 @@ interface DarkThemeConfig { borderAccent: string; accent: string; accentBright: string; + destructive: string; } const darkTerminalAnsi = { @@ -276,7 +277,7 @@ function buildDarkSemanticColors(tint: DarkThemeConfig) { accentBright: tint.accentBright, accentForeground: "#ffffff", - destructive: "#ef4444", + destructive: tint.destructive, destructiveForeground: "#ffffff", success: tint.accent, successForeground: "#ffffff", @@ -332,6 +333,7 @@ const paseoDarkColors = buildDarkSemanticColors({ borderAccent: "#2F3534", accent: "#20744A", accentBright: "#7ccba0", + destructive: "#c64f43", // warm red, hue ~7 — reads as red (not pink) against the green tint }); // Zinc — neutral gray, no tint @@ -350,6 +352,7 @@ const zincDarkColors = buildDarkSemanticColors({ borderAccent: "#303036", accent: "#20744A", accentBright: "#7ccba0", + destructive: "#c44a4a", // neutral red, hue 0 — clearly red without screaming }); // Midnight — subtle blue tint @@ -368,6 +371,7 @@ const midnightDarkColors = buildDarkSemanticColors({ borderAccent: "#2e3040", accent: "#3b6fcf", accentBright: "#7eaaeb", + destructive: "#c44a52", // red with a hint of cool lean against the blue tint }); // Claude — warm neutral with subtle orange undertone @@ -386,6 +390,7 @@ const claudeDarkColors = buildDarkSemanticColors({ borderAccent: "#36332f", accent: "#d97757", accentBright: "#e89a7f", + destructive: "#cf513e", // warm orange-red, hue ~10 — sits with the Claude orange accent }); // Ghostty — blue-tinted dark based on Ghostty default background @@ -404,6 +409,7 @@ const ghosttyDarkColors = buildDarkSemanticColors({ borderAccent: "#3f4454", accent: "#89b4fa", accentBright: "#b4d0fc", + destructive: "#c44a55", // red with slight cool lean against the slate-blue surfaces }); const commonTheme = {