Last active
December 26, 2025 10:51
-
-
Save subtleGradient/fd3d1ef74053d9ed3481b84a2c198751 to your computer and use it in GitHub Desktop.
VT-HIG Cheat Sheet -- Virtual Terminal Human Interface Guidelines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react" | |
| // VT-HIG Cheat Sheet — v0.3 | |
| // Goal: a compact, highly organized reference for CLI/TUI UX contracts. | |
| // This is not about rich text editing; focus is navigation + viewing + selection. | |
| export default function VTHIGCheatSheet() { | |
| return ( | |
| <div style={s.page}> | |
| {/* Sticky header keeps the “what is this?” context while scanning sections */} | |
| <Header /> | |
| <main style={s.main}> | |
| <Section title="00 · The Web of Lies (Capability Stack)"> | |
| <StackCard /> | |
| </Section> | |
| <Section title="01 · Contracts"> | |
| <Grid columns={2}> | |
| <Card title="CLI contract" subtitle="streams + exit codes + composability"> | |
| <Bullets | |
| items={[ | |
| "stdout = data", | |
| "stderr = diagnostics", | |
| "exit code = success/failure", | |
| "--help always works", | |
| "--dry-run / --verbose build trust", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="TUI contract" subtitle="state → render loop + keyboard-first"> | |
| <Bullets | |
| items={[ | |
| "single full-screen surface", | |
| "byte-stream substrate (TTY/PTY)", | |
| "status line = toast", | |
| "mode indicator always visible", | |
| "fast path = memorized keys", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="02 · Patterns (Binary)"> | |
| <Grid columns={2}> | |
| <Binary | |
| leftTitle="Navigation" | |
| leftItems={[ | |
| ["Drill-in", "Enter / open detail"], | |
| ["Back", "Esc / q (consistent)"], | |
| ["Focus", "highlight selected row"], | |
| ["Panes", "Tab cycles focus"], | |
| ]} | |
| rightTitle="Input" | |
| rightItems={[ | |
| ["Modes", "Normal vs Insert"], | |
| ["Cancel", "Esc cancels; Ctrl+C interrupts"], | |
| ["Filter", "type-to-filter"], | |
| ["Search", "/ then n/N"], | |
| ]} | |
| /> | |
| <Binary | |
| leftTitle="Feedback" | |
| leftItems={[ | |
| ["Status line", "non-modal confirmations"], | |
| ["Errors", "persist until replaced"], | |
| ["Progress", "spinner/bar on stderr"], | |
| ["Explain", "--verbose / logs"], | |
| ]} | |
| rightTitle="Safety" | |
| rightItems={[ | |
| ["Dry run", "preview the mutation"], | |
| ["Confirm", "only destructive"], | |
| ["Undo", "when feasible"], | |
| ["Idempotent", "safe under interrupts"], | |
| ]} | |
| /> | |
| </Grid> | |
| </Section> | |
| <Section title="03 · Keys (Expected Defaults)"> | |
| <Grid columns={2}> | |
| <Card title="Universal" subtitle="muscle-memory contracts"> | |
| <KeyList | |
| items={[ | |
| ["Esc", "cancel / back / exit overlay"], | |
| ["Ctrl+C", "interrupt current op"], | |
| ["?", "help"], | |
| ["/", "search"], | |
| ["n / N", "next / prev match"], | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Lists" subtitle="selection + narrowing"> | |
| <KeyList | |
| items={[ | |
| ["↑↓ / j k", "move selection"], | |
| ["Enter", "open / confirm"], | |
| ["Type", "filter query"], | |
| ["Backspace", "edit query"], | |
| ["Tab", "next pane"], | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="04 · Accessibility"> | |
| <Grid columns={2}> | |
| <Binary | |
| leftTitle="Screen reader mode" | |
| leftItems={[ | |
| ["Prefer", "stable prompts + linear text"], | |
| ["Avoid", "rapid full-screen animation"], | |
| ["Provide", "--no-tui / --plain / --json"], | |
| ["Announce", "mode + focus changes"], | |
| ]} | |
| rightTitle="Low-vision mode" | |
| rightItems={[ | |
| ["Prefer", "high-contrast themes"], | |
| ["Avoid", "color-only meaning"], | |
| ["Provide", "fewer panes (zoom by simplification)"], | |
| ["Support", "monochrome fallback"], | |
| ]} | |
| /> | |
| <Binary | |
| leftTitle="Braille / tactile" | |
| leftItems={[ | |
| ["Prefer", "predictable reading order"], | |
| ["Avoid", "cursor teleport noise"], | |
| ["Provide", "single-pane detail view"], | |
| ["Expose", "copyable summaries"], | |
| ]} | |
| rightTitle="Motor / fatigue" | |
| rightItems={[ | |
| ["Prefer", "few essential keys"], | |
| ["Avoid", "tight chords + timing combos"], | |
| ["Provide", "command palette / verbs"], | |
| ["Support", "keybind remapping"], | |
| ]} | |
| /> | |
| </Grid> | |
| <Grid columns={2}> | |
| <Card title="How blind users interact" subtitle="what viewing TUIs must respect"> | |
| <Bullets | |
| items={[ | |
| "Often via a screen reader that reads the terminal buffer (or a Braille display)", | |
| "They depend on linear text, not spatial layouts", | |
| "Frequent redraws can sound like ‘everything changed’ repeatedly", | |
| "Provide a plain/CLI view for the same data whenever possible", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Concrete accessibility affordances" subtitle="terminal-friendly (no ARIA)"> | |
| <Bullets | |
| items={[ | |
| "Add --accessibility (reduced motion + fewer panes)", | |
| "Keep a stable status line summary (mode, focus, selection)", | |
| "Add “describe current selection” as a command", | |
| "Never encode meaning only by color (use symbols + words)", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="05 · Non-keyboard Events → Typical Reactions"> | |
| <Grid columns={2}> | |
| <Binary | |
| leftTitle="Inside the TTY" | |
| leftItems={[ | |
| ["SIGWINCH", "reflow layout; redraw"], | |
| ["SIGINT", "cancel op; keep state"], | |
| ["SIGTERM", "flush/cleanup; exit"], | |
| ["SIGHUP", "treat as disconnect"], | |
| ]} | |
| rightTitle="Outside the TTY" | |
| rightItems={[ | |
| ["Focus lost", "pause hotkeys; degrade"], | |
| ["VT switch", "stop drawing; resume"], | |
| ["SSH lag", "avoid rapid redraw"], | |
| ["Theme change", "repaint palette"], | |
| ]} | |
| /> | |
| <Binary | |
| leftTitle="I/O & Time" | |
| leftItems={[ | |
| ["Timer tick", "animate spinner; minimal"], | |
| ["Async update", "patch region"], | |
| ["Child exit", "show status; refresh"], | |
| ["FS change", "invalidate cache"], | |
| ]} | |
| rightTitle="Pointer & Paste" | |
| rightItems={[ | |
| ["Mouse", "optional; never required"], | |
| ["Wheel", "scroll; keep selection"], | |
| ["Paste", "treat as text; rate-limit"], | |
| ["Bracketed paste", "avoid triggering keybinds"], | |
| ]} | |
| /> | |
| </Grid> | |
| </Section> | |
| <Section title="06 · Redraw Strategy (Full vs Surgical)"> | |
| <Grid columns={2}> | |
| <Card title="Full redraw triggers" subtitle="cheap correctness beats cleverness"> | |
| <Bullets | |
| items={[ | |
| "terminal resize (SIGWINCH)", | |
| "theme/palette change", | |
| "layout mode change (split/merge panes)", | |
| "screen corruption / unknown state", | |
| "unicode width assumptions changed", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Surgical update triggers" subtitle="small change, known region"> | |
| <Bullets | |
| items={[ | |
| "cursor move + highlight change", | |
| "status line message update", | |
| "spinner/progress tick", | |
| "one row in a list changes", | |
| "append-only log tail in a pane", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| <Grid columns={2}> | |
| <Binary | |
| leftTitle="Full redraw" | |
| leftItems={[ | |
| ["Guarantee", "screen = render(state)"], | |
| ["Cost", "O(rows×cols)"], | |
| ["Best for", "fragile/remote terminals"], | |
| ["UX", "stable + predictable"], | |
| ]} | |
| rightTitle="Surgical redraw" | |
| rightItems={[ | |
| ["Guarantee", "patch screen to match state"], | |
| ["Cost", "O(changed cells/rects)"], | |
| ["Best for", "high-frequency dashboards"], | |
| ["UX", "snappy + less flicker"], | |
| ]} | |
| /> | |
| <Card title="Heuristic" subtitle="choose based on uncertainty"> | |
| <pre style={s.pre}>{`if (screenStateUnknown) fullRedraw() | |
| else if (layoutChanged) fullRedraw() | |
| else patch(changedRects)`}</pre> | |
| <div style={s.note}> | |
| If you can’t prove which cells are correct, you can’t patch safely. | |
| </div> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="07 · Text Selection (Viewing / Navigation)"> | |
| <Grid columns={2}> | |
| <Binary | |
| leftTitle="Primary models" | |
| leftItems={[ | |
| ["Range", "start/end positions in a buffer"], | |
| ["Rect", "columnar selection (rare)"], | |
| ["Line", "whole-line selection"], | |
| ["Token", "word/path selection"], | |
| ]} | |
| rightTitle="Primary intents" | |
| rightItems={[ | |
| ["Copy", "send to clipboard or stdout"], | |
| ["Quote", "copy with context/line numbers"], | |
| ["Search", "promote selection → / query"], | |
| ["Navigate", "jump to selection target"], | |
| ]} | |
| /> | |
| <Binary | |
| leftTitle="Selection lifecycle" | |
| leftItems={[ | |
| ["Enter", "start selecting"], | |
| ["Adjust", "expand/shrink with keys"], | |
| ["Confirm", "copy/action"], | |
| ["Exit", "Esc cancels; clears highlight"], | |
| ]} | |
| rightTitle="Non-goals" | |
| rightItems={[ | |
| ["No rich edit", "editing UX is out of scope"], | |
| ["No layout", "selection is on text, not pixels"], | |
| ["No mouse", "mouse optional only"], | |
| ["No surprises", "never steal focus"], | |
| ]} | |
| /> | |
| </Grid> | |
| <Grid columns={2}> | |
| <Card title="Expected patterns" subtitle="document viewing (less, man, logs)"> | |
| <Bullets | |
| items={[ | |
| "Two modes: browse vs select (selection is explicit)", | |
| "Selection is visible: inverse/highlight band over text range", | |
| "Copy is a verb: y / c / Enter in a copy dialog", | |
| "Search-from-selection: promote selection into / prompt", | |
| "Optional: add line numbers for stable quoting", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Clipboard reality" subtitle="multiple viable paths"> | |
| <Bullets | |
| items={[ | |
| "Minimum: copy prints to stdout (user pipes it)", | |
| "Enhanced: OSC-52 clipboard (works over SSH in many emulators)", | |
| "Enhanced: selection to a temp file path", | |
| "Never: require mouse for copying", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="08 · Minimum vs Enhanced (Portability)"> | |
| <Grid columns={2}> | |
| <Card title="Minimum substrate" subtitle="works everywhere"> | |
| <Bullets | |
| items={[ | |
| "ASCII first; unicode optional", | |
| "no mouse required", | |
| "no color dependency", | |
| "no hover assumptions", | |
| "stateless redraw tolerated", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Enhanced substrate" subtitle="nice-to-have"> | |
| <Bullets | |
| items={[ | |
| "truecolor themes", | |
| "mouse support (optional)", | |
| "inline images (kitty/iterm)", | |
| "OSC-52 clipboard", | |
| "hyperlinks (OSC-8)", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Section title="09 · Anti-patterns"> | |
| <Grid columns={2}> | |
| <Card title="Surprise" subtitle="breaks trust"> | |
| <Bullets | |
| items={[ | |
| "mode switches without indicator", | |
| "destructive defaults without preview", | |
| "progress mixed into stdout", | |
| "key chords that depend on timing", | |
| ]} | |
| /> | |
| </Card> | |
| <Card title="Fragility" subtitle="breaks portability"> | |
| <Bullets | |
| items={[ | |
| "requires unicode for core affordances", | |
| "requires mouse to navigate", | |
| "assumes fixed terminal size", | |
| "breaks under tmux/ssh/resizes", | |
| ]} | |
| /> | |
| </Card> | |
| </Grid> | |
| </Section> | |
| <Footer /> | |
| </main> | |
| </div> | |
| ) | |
| } | |
| function Header() { | |
| return ( | |
| <header style={s.header}> | |
| <div style={s.hRow}> | |
| <div> | |
| {/* Keep the subtitle short; this is a cheat sheet, not a manifesto */} | |
| <div style={s.hTitle}>VT-HIG — Cheat Sheet</div> | |
| <div style={s.hSub}>CLI/TUI Human Interface Guidelines (draft) 😎</div> | |
| </div> | |
| {/* Version pill is a simple visual anchor for iteration */} | |
| <div style={s.pill}>v0.3</div> | |
| </div> | |
| </header> | |
| ) | |
| } | |
| function Footer() { | |
| return ( | |
| <div style={s.footer}> | |
| <div style={s.footerLeft}> | |
| <span style={s.mono}>Principle:</span> ship a reliable contract on a dumb byte | |
| stream. | |
| </div> | |
| <div style={s.footerRight}> | |
| <span style={s.mono}>Pattern format:</span> intent → contract → keys → failure | |
| modes. | |
| </div> | |
| </div> | |
| ) | |
| } | |
| function Section({ | |
| title, | |
| children, | |
| }: { | |
| title: string | |
| children: React.ReactNode | |
| }) { | |
| return ( | |
| <section style={s.section}> | |
| {/* Sections are the “table of contents” without needing an actual ToC */} | |
| <div style={s.sectionTitle}>{title}</div> | |
| <div style={s.sectionBody}>{children}</div> | |
| </section> | |
| ) | |
| } | |
| function Grid({ | |
| columns, | |
| children, | |
| }: { | |
| columns: 1 | 2 | 3 | |
| children: React.ReactNode | |
| }) { | |
| return ( | |
| <div | |
| style={{ | |
| display: "grid", | |
| // Use equal-width columns for scanability (cheat sheets are for scanning). | |
| gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, | |
| gap: 12, | |
| }} | |
| > | |
| {children} | |
| </div> | |
| ) | |
| } | |
| function Card({ | |
| title, | |
| subtitle, | |
| children, | |
| }: { | |
| title: string | |
| subtitle?: string | |
| children: React.ReactNode | |
| }) { | |
| return ( | |
| <div style={s.card}> | |
| {/* Card head is intentionally compact to keep “less is more” */} | |
| <div style={s.cardHead}> | |
| <div style={s.cardTitle}>{title}</div> | |
| {subtitle ? <div style={s.cardSub}>{subtitle}</div> : null} | |
| </div> | |
| <div style={s.cardBody}>{children}</div> | |
| </div> | |
| ) | |
| } | |
| function Bullets({ items }: { items: string[] }) { | |
| return ( | |
| <ul style={s.ul}> | |
| {items.map((t) => ( | |
| // Using the bullet string as key is fine here: static, short lists. | |
| <li key={t} style={s.li}> | |
| {t} | |
| </li> | |
| ))} | |
| </ul> | |
| ) | |
| } | |
| function KeyList({ items }: { items: Array<[string, string]> }) { | |
| return ( | |
| <div style={{ display: "grid", gap: 8 }}> | |
| {items.map(([k, v]) => ( | |
| // Key labels are unique enough in this list; good keys keep React diffing cheap. | |
| <div key={k} style={s.kRow}> | |
| <kbd style={s.kbd}>{k}</kbd> | |
| <div style={s.kText}>{v}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ) | |
| } | |
| function Binary({ | |
| leftTitle, | |
| leftItems, | |
| rightTitle, | |
| rightItems, | |
| }: { | |
| leftTitle: string | |
| leftItems: Array<[string, string]> | |
| rightTitle: string | |
| rightItems: Array<[string, string]> | |
| }) { | |
| return ( | |
| <div style={s.binary}> | |
| {/* Binary blocks enforce a “two buckets” mental model (your preference). */} | |
| <div style={s.binaryCol}> | |
| <div style={s.binaryTitle}>{leftTitle}</div> | |
| <Pairs items={leftItems} /> | |
| </div> | |
| <div style={s.binaryDivider} /> | |
| <div style={s.binaryCol}> | |
| <div style={s.binaryTitle}>{rightTitle}</div> | |
| <Pairs items={rightItems} /> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| function Pairs({ items }: { items: Array<[string, string]> }) { | |
| return ( | |
| <div style={{ display: "grid", gap: 8 }}> | |
| {items.map(([k, v]) => ( | |
| // Pair key is used as React key because the left column is a label. | |
| <div key={k} style={s.pair}> | |
| <div style={s.pairKey}>{k}</div> | |
| <div style={s.pairVal}>{v}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ) | |
| } | |
| function StackCard() { | |
| return ( | |
| <Card title="Capability stack" subtitle="where the UX contract actually sits"> | |
| {/* Preformatted so the arrows align as a visual ladder */} | |
| <pre style={s.pre}> | |
| {`[User Intent] | |
| ↓ | |
| [App UX contract] | |
| ↓ | |
| [Library fiction] | |
| ↓ | |
| [TTY/PTY contract] | |
| ↓ | |
| [Terminal emulator fiction] | |
| ↓ | |
| [OS + kernel] | |
| ↓ | |
| [Hardware]`}</pre> | |
| <div style={s.note}> | |
| Design patterns live at <span style={s.mono}>App UX contract</span>, | |
| constrained by everything below. | |
| </div> | |
| </Card> | |
| ) | |
| } | |
| // Styling: inline only (single-file cheat sheet; easy to paste into any sandbox). | |
| // Keep the palette minimal to avoid turning this into a theme project. | |
| const s: Record<string, React.CSSProperties> = { | |
| page: { | |
| minHeight: "100vh", | |
| background: "#0b0d10", | |
| color: "#e9edf2", | |
| fontFamily: | |
| "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial", | |
| }, | |
| header: { | |
| position: "sticky", | |
| top: 0, | |
| zIndex: 5, | |
| background: "rgba(11, 13, 16, 0.92)", | |
| backdropFilter: "blur(10px)", | |
| borderBottom: "1px solid rgba(233, 237, 242, 0.10)", | |
| }, | |
| hRow: { | |
| maxWidth: 980, | |
| margin: "0 auto", | |
| padding: "18px 16px", | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "space-between", | |
| gap: 12, | |
| }, | |
| hTitle: { | |
| fontSize: 18, | |
| fontWeight: 700, | |
| letterSpacing: 0.2, | |
| }, | |
| hSub: { | |
| marginTop: 4, | |
| fontSize: 12, | |
| color: "rgba(233, 237, 242, 0.70)", | |
| }, | |
| pill: { | |
| fontSize: 12, | |
| padding: "6px 10px", | |
| borderRadius: 999, | |
| border: "1px solid rgba(233, 237, 242, 0.16)", | |
| color: "rgba(233, 237, 242, 0.85)", | |
| }, | |
| main: { | |
| maxWidth: 980, | |
| margin: "0 auto", | |
| padding: "14px 16px 32px", | |
| display: "grid", | |
| gap: 14, | |
| }, | |
| section: { | |
| display: "grid", | |
| gap: 10, | |
| }, | |
| sectionTitle: { | |
| fontSize: 13, | |
| fontWeight: 700, | |
| color: "rgba(233, 237, 242, 0.90)", | |
| letterSpacing: 0.2, | |
| }, | |
| sectionBody: {}, | |
| card: { | |
| borderRadius: 16, | |
| border: "1px solid rgba(233, 237, 242, 0.12)", | |
| background: "rgba(255, 255, 255, 0.03)", | |
| overflow: "hidden", | |
| }, | |
| cardHead: { | |
| padding: "12px 12px 0", | |
| }, | |
| cardTitle: { | |
| fontSize: 13, | |
| fontWeight: 700, | |
| }, | |
| cardSub: { | |
| marginTop: 4, | |
| fontSize: 12, | |
| color: "rgba(233, 237, 242, 0.70)", | |
| }, | |
| cardBody: { | |
| padding: 12, | |
| }, | |
| ul: { | |
| margin: 0, | |
| paddingLeft: 18, | |
| display: "grid", | |
| gap: 6, | |
| }, | |
| li: { | |
| color: "rgba(233, 237, 242, 0.88)", | |
| fontSize: 12, | |
| lineHeight: 1.35, | |
| }, | |
| kRow: { | |
| display: "grid", | |
| gridTemplateColumns: "auto 1fr", | |
| gap: 10, | |
| alignItems: "center", | |
| }, | |
| kbd: { | |
| fontFamily: | |
| "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New'", | |
| fontSize: 12, | |
| padding: "4px 8px", | |
| borderRadius: 10, | |
| border: "1px solid rgba(233, 237, 242, 0.14)", | |
| background: "rgba(0,0,0,0.25)", | |
| color: "rgba(233, 237, 242, 0.92)", | |
| justifySelf: "start", | |
| }, | |
| kText: { | |
| fontSize: 12, | |
| color: "rgba(233, 237, 242, 0.82)", | |
| lineHeight: 1.35, | |
| }, | |
| binary: { | |
| display: "grid", | |
| gridTemplateColumns: "1fr 1px 1fr", | |
| borderRadius: 16, | |
| border: "1px solid rgba(233, 237, 242, 0.12)", | |
| background: "rgba(255, 255, 255, 0.03)", | |
| overflow: "hidden", | |
| }, | |
| binaryCol: { | |
| padding: 12, | |
| display: "grid", | |
| gap: 10, | |
| }, | |
| binaryDivider: { | |
| background: "rgba(233, 237, 242, 0.10)", | |
| }, | |
| binaryTitle: { | |
| fontSize: 12, | |
| fontWeight: 700, | |
| color: "rgba(233, 237, 242, 0.88)", | |
| }, | |
| pair: { | |
| display: "grid", | |
| gridTemplateColumns: "110px 1fr", | |
| gap: 10, | |
| alignItems: "baseline", | |
| }, | |
| pairKey: { | |
| fontSize: 12, | |
| fontWeight: 700, | |
| color: "rgba(233, 237, 242, 0.92)", | |
| }, | |
| pairVal: { | |
| fontSize: 12, | |
| color: "rgba(233, 237, 242, 0.78)", | |
| lineHeight: 1.35, | |
| }, | |
| pre: { | |
| margin: 0, | |
| padding: 12, | |
| borderRadius: 14, | |
| background: "rgba(0,0,0,0.28)", | |
| border: "1px solid rgba(233, 237, 242, 0.10)", | |
| fontSize: 12, | |
| lineHeight: 1.35, | |
| overflowX: "auto", | |
| fontFamily: | |
| "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New'", | |
| color: "rgba(233, 237, 242, 0.92)", | |
| }, | |
| note: { | |
| marginTop: 10, | |
| fontSize: 12, | |
| color: "rgba(233, 237, 242, 0.75)", | |
| lineHeight: 1.35, | |
| }, | |
| mono: { | |
| fontFamily: | |
| "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New'", | |
| color: "rgba(233, 237, 242, 0.92)", | |
| }, | |
| footer: { | |
| marginTop: 8, | |
| paddingTop: 14, | |
| borderTop: "1px solid rgba(233, 237, 242, 0.10)", | |
| display: "flex", | |
| flexWrap: "wrap", | |
| gap: 10, | |
| justifyContent: "space-between", | |
| color: "rgba(233, 237, 242, 0.70)", | |
| fontSize: 12, | |
| }, | |
| footerLeft: { | |
| flex: "1 1 360px", | |
| }, | |
| footerRight: { | |
| flex: "1 1 360px", | |
| textAlign: "right", | |
| }, | |
| } |
Author
Author
lazygit uses [ / ] to switch between tabs
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://chatgpt.com/canvas/shared/6949945305248191a68d87cf848f41c6