@wrongstack/tui 0.305.0 → 0.305.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -116,18 +116,20 @@ export declare function formatDiffStats(added: number, removed: number): string
116
116
  /**
117
117
  * Return the token list to render on a diff wash (`diffAddBg` /
118
118
  * `diffDelBg`). When `onWash` is false the input is returned unchanged —
119
- * plain-background rendering keeps the conventional dim/gray comment
120
- * look and there is no contrast reason to intervene.
119
+ * plain-background rendering keeps the conventional dim comment look and
120
+ * there is no contrast reason to intervene.
121
121
  *
122
- * When `onWash` is true, comment tokens (color `gray`, conventionally
123
- * `dim: true`) are promoted to a brighter foreground (`grayBright`) and
124
- * lose their dim flag. The diff wash is dark enough that `dim gray`
125
- * (`pastel.gray`) falls below WCAG AA contrast on either `theme.diffAddBg`
126
- * (ratio ≈ 2.91) or `theme.diffDelBg` (ratio ≈ 3.47) and reads as near-
127
- * invisible noise; `grayBright` clears both wash backgrounds (≥ 4.5) and
128
- * keeps the comment visually secondary without becoming unreadable.
129
- * Non-comment tokens pass through unchanged so the rest of the line keeps
130
- * its existing syntax palette on the wash.
122
+ * When `onWash` is true, comment tokens are re-pointed from the
123
+ * `syntax.comment` role to `syntax.commentOnWash` and lose their dim flag.
124
+ * `syntax.comment` resolves to `theme.textMuted`, which is chosen to recede
125
+ * against `theme.surface` and therefore falls below WCAG AA on either wash;
126
+ * `syntax.commentOnWash` resolves to `theme.textSecondary`, which clears both
127
+ * (≥ 4.5) while keeping the comment visually secondary. Because both sides
128
+ * are ROLES, the promotion follows `/theme` automatically instead of pinning
129
+ * one hardcoded grey for all 35 presets.
130
+ *
131
+ * Non-comment tokens pass through unchanged so the rest of the line keeps its
132
+ * syntax palette on the wash.
131
133
  *
132
134
  * Exported for unit testing — `renderTokens` calls it before mapping to
133
135
  * `<Text>` elements so the override logic itself stays a pure function.
package/dist/index.js CHANGED
@@ -315,6 +315,12 @@ var pastel = Object.freeze({
315
315
  // Greys
316
316
  gray: "#7f849c",
317
317
  grey: "#7f849c",
318
+ // Lifted grey used for comment tokens sitting on a diff wash. `gray` falls
319
+ // below WCAG AA on `diffAddBg`/`diffDelBg`; this clears both. MUST exist in
320
+ // this map — `softColor` passes unknown names through untouched and Ink /
321
+ // chalk then silently drop them, rendering the token with no color at all.
322
+ grayBright: "#a6adc8",
323
+ greyBright: "#a6adc8",
318
324
  // Bright variants — a touch lighter / shifted within the same family
319
325
  blackBright: "#585b70",
320
326
  redBright: "#eba0ac",
@@ -334,8 +340,32 @@ var pastel = Object.freeze({
334
340
  flamingo: "#f2cdcd"
335
341
  });
336
342
  var catppuccin = pastel;
343
+ var SYNTAX_TOKEN = Object.freeze({
344
+ keyword: "brand",
345
+ string: "success",
346
+ comment: "textMuted",
347
+ commentOnWash: "textSecondary",
348
+ number: "warn",
349
+ literal: "warn",
350
+ property: "accent",
351
+ variable: "accent",
352
+ command: "accent",
353
+ flag: "warn",
354
+ decorator: "brand",
355
+ diffAdd: "success",
356
+ diffDel: "error",
357
+ diffMeta: "accent"
358
+ });
359
+ var SYNTAX_PREFIX = "syntax.";
360
+ function resolveSyntaxColor(role, palette = theme) {
361
+ return palette.syntax?.[role] ?? palette[SYNTAX_TOKEN[role]];
362
+ }
337
363
  function softColor(color) {
338
364
  if (!color) return color;
365
+ if (color.startsWith(SYNTAX_PREFIX)) {
366
+ const role = color.slice(SYNTAX_PREFIX.length);
367
+ return role in SYNTAX_TOKEN ? resolveSyntaxColor(role) : color;
368
+ }
339
369
  return pastel[color] ?? color;
340
370
  }
341
371
  function detectSupportsBackground(env = process.env, isTTY = process.stdout.isTTY ?? false) {
@@ -408,6 +438,9 @@ function setActiveTheme(name) {
408
438
  }
409
439
  const t = theme;
410
440
  const p = preset;
441
+ for (const k of Object.keys(t)) {
442
+ if (!(k in p)) delete t[k];
443
+ }
411
444
  for (const k of Object.keys(p)) {
412
445
  t[k] = p[k];
413
446
  }
@@ -459,7 +492,9 @@ var themePresets = Object.freeze({
459
492
  brand: "#bb9af7",
460
493
  surface: "#16161e",
461
494
  surfaceRaised: "#1a1b26",
462
- monitor: { ...baseTheme.monitor, fleet: "#7dcfff", agents: "#bb9af7", goal: "#9ece6a" }
495
+ diffAddBg: "#1c3326",
496
+ diffDelBg: "#37202b",
497
+ monitor: { fleet: "#7dcfff", agents: "#bb9af7", worktree: "#9ece6a", phase: "#7dcfff" }
463
498
  }),
464
499
  nord: Object.freeze({
465
500
  ...baseTheme,
@@ -478,7 +513,9 @@ var themePresets = Object.freeze({
478
513
  brand: "#b48ead",
479
514
  surface: "#2e3440",
480
515
  surfaceRaised: "#3b4252",
481
- monitor: { ...baseTheme.monitor, fleet: "#88c0d0", agents: "#b48ead", goal: "#a3be8c" }
516
+ diffAddBg: "#2f4034",
517
+ diffDelBg: "#442f35",
518
+ monitor: { fleet: "#88c0d0", agents: "#b48ead", worktree: "#a3be8c", phase: "#88c0d0" }
482
519
  }),
483
520
  cyberpunk: Object.freeze({
484
521
  ...baseTheme,
@@ -497,7 +534,9 @@ var themePresets = Object.freeze({
497
534
  brand: "#ff00aa",
498
535
  surface: "#0d0d1a",
499
536
  surfaceRaised: "#16162a",
500
- monitor: { ...baseTheme.monitor, fleet: "#00f0ff", agents: "#ff00aa", goal: "#00ff66" }
537
+ diffAddBg: "#0d2e1f",
538
+ diffDelBg: "#2e0d1c",
539
+ monitor: { fleet: "#00f0ff", agents: "#ff00aa", worktree: "#00ff66", phase: "#00f0ff" }
501
540
  }),
502
541
  dracula: Object.freeze({
503
542
  ...baseTheme,
@@ -516,7 +555,9 @@ var themePresets = Object.freeze({
516
555
  brand: "#ff79c6",
517
556
  surface: "#282a36",
518
557
  surfaceRaised: "#44475a",
519
- monitor: { ...baseTheme.monitor, fleet: "#8be9fd", agents: "#ff79c6", goal: "#50fa7b" }
558
+ diffAddBg: "#26402e",
559
+ diffDelBg: "#43262e",
560
+ monitor: { fleet: "#8be9fd", agents: "#ff79c6", worktree: "#50fa7b", phase: "#8be9fd" }
520
561
  }),
521
562
  "gruvbox-dark": Object.freeze({
522
563
  ...baseTheme,
@@ -537,7 +578,7 @@ var themePresets = Object.freeze({
537
578
  surfaceRaised: "#3c3836",
538
579
  diffAddBg: "#1f3a1f",
539
580
  diffDelBg: "#3a1f1f",
540
- monitor: { ...baseTheme.monitor, fleet: "#83a598", agents: "#d3869b", goal: "#b8bb26" }
581
+ monitor: { fleet: "#83a598", agents: "#d3869b", worktree: "#b8bb26", phase: "#83a598" }
541
582
  }),
542
583
  "solarized-dark": Object.freeze({
543
584
  ...baseTheme,
@@ -558,7 +599,7 @@ var themePresets = Object.freeze({
558
599
  surfaceRaised: "#073642",
559
600
  diffAddBg: "#0e3a30",
560
601
  diffDelBg: "#3a1a1a",
561
- monitor: { ...baseTheme.monitor, fleet: "#268bd2", agents: "#d33682", goal: "#859900" }
602
+ monitor: { fleet: "#268bd2", agents: "#d33682", worktree: "#859900", phase: "#268bd2" }
562
603
  }),
563
604
  "one-dark": Object.freeze({
564
605
  ...baseTheme,
@@ -579,7 +620,7 @@ var themePresets = Object.freeze({
579
620
  surfaceRaised: "#282c34",
580
621
  diffAddBg: "#1f3a26",
581
622
  diffDelBg: "#3a2226",
582
- monitor: { ...baseTheme.monitor, fleet: "#61afef", agents: "#c678dd", goal: "#98c379" }
623
+ monitor: { fleet: "#61afef", agents: "#c678dd", worktree: "#98c379", phase: "#61afef" }
583
624
  }),
584
625
  monokai: Object.freeze({
585
626
  ...baseTheme,
@@ -600,7 +641,7 @@ var themePresets = Object.freeze({
600
641
  surfaceRaised: "#272822",
601
642
  diffAddBg: "#1f3318",
602
643
  diffDelBg: "#3a1f25",
603
- monitor: { ...baseTheme.monitor, fleet: "#66d9ef", agents: "#ae81ff", goal: "#a6e22e" }
644
+ monitor: { fleet: "#66d9ef", agents: "#ae81ff", worktree: "#a6e22e", phase: "#66d9ef" }
604
645
  }),
605
646
  "rose-pine": Object.freeze({
606
647
  ...baseTheme,
@@ -621,7 +662,7 @@ var themePresets = Object.freeze({
621
662
  surfaceRaised: "#1f1d2e",
622
663
  diffAddBg: "#1f3538",
623
664
  diffDelBg: "#3a2530",
624
- monitor: { ...baseTheme.monitor, fleet: "#9ccfd8", agents: "#c4a7e7", goal: "#31748f" }
665
+ monitor: { fleet: "#9ccfd8", agents: "#c4a7e7", worktree: "#31748f", phase: "#9ccfd8" }
625
666
  }),
626
667
  kanagawa: Object.freeze({
627
668
  ...baseTheme,
@@ -642,7 +683,7 @@ var themePresets = Object.freeze({
642
683
  surfaceRaised: "#1f1f28",
643
684
  diffAddBg: "#1f2d1f",
644
685
  diffDelBg: "#321e22",
645
- monitor: { ...baseTheme.monitor, fleet: "#7fb4ca", agents: "#957fb8", goal: "#98bb6e" }
686
+ monitor: { fleet: "#7fb4ca", agents: "#957fb8", worktree: "#98bb6e", phase: "#7fb4ca" }
646
687
  }),
647
688
  "ayu-dark": Object.freeze({
648
689
  ...baseTheme,
@@ -663,7 +704,7 @@ var themePresets = Object.freeze({
663
704
  surfaceRaised: "#14181f",
664
705
  diffAddBg: "#1c2a1f",
665
706
  diffDelBg: "#2e1c20",
666
- monitor: { ...baseTheme.monitor, fleet: "#59c2ff", agents: "#d4b5ff", goal: "#aad94c" }
707
+ monitor: { fleet: "#59c2ff", agents: "#d4b5ff", worktree: "#aad94c", phase: "#59c2ff" }
667
708
  }),
668
709
  everforest: Object.freeze({
669
710
  ...baseTheme,
@@ -684,7 +725,7 @@ var themePresets = Object.freeze({
684
725
  surfaceRaised: "#343f44",
685
726
  diffAddBg: "#1f3326",
686
727
  diffDelBg: "#331f25",
687
- monitor: { ...baseTheme.monitor, fleet: "#7fbbb3", agents: "#d699b6", goal: "#a7c080" }
728
+ monitor: { fleet: "#7fbbb3", agents: "#d699b6", worktree: "#a7c080", phase: "#7fbbb3" }
688
729
  }),
689
730
  "night-owl": Object.freeze({
690
731
  ...baseTheme,
@@ -705,7 +746,7 @@ var themePresets = Object.freeze({
705
746
  surfaceRaised: "#0b253a",
706
747
  diffAddBg: "#0e2e26",
707
748
  diffDelBg: "#2e1722",
708
- monitor: { ...baseTheme.monitor, fleet: "#82aaff", agents: "#c792ea", goal: "#c3e88d" }
749
+ monitor: { fleet: "#82aaff", agents: "#c792ea", worktree: "#c3e88d", phase: "#82aaff" }
709
750
  }),
710
751
  synthwave: Object.freeze({
711
752
  ...baseTheme,
@@ -726,7 +767,508 @@ var themePresets = Object.freeze({
726
767
  surfaceRaised: "#241b46",
727
768
  diffAddBg: "#1f3530",
728
769
  diffDelBg: "#3a1f30",
729
- monitor: { ...baseTheme.monitor, fleet: "#36f9f6", agents: "#b56cff", goal: "#72f1b8" }
770
+ monitor: { fleet: "#36f9f6", agents: "#b56cff", worktree: "#72f1b8", phase: "#36f9f6" }
771
+ }),
772
+ "github-dark": Object.freeze({
773
+ ...baseTheme,
774
+ textPrimary: "#c9d1d9",
775
+ textSecondary: "#b1bac4",
776
+ textMuted: "#8b949e",
777
+ brandPrimary: "#d29922",
778
+ brandAccent: "#bc8cff",
779
+ accent: "#58a6ff",
780
+ user: "#d29922",
781
+ assistant: "#58a6ff",
782
+ tool: "#39c5cf",
783
+ success: "#3fb950",
784
+ warn: "#d29922",
785
+ error: "#f85149",
786
+ borderDefault: "#30363d",
787
+ borderSubtle: "#21262d",
788
+ borderActive: "#58a6ff",
789
+ brand: "#bc8cff",
790
+ surface: "#0d1117",
791
+ surfaceRaised: "#161b22",
792
+ diffAddBg: "#0d2f18",
793
+ diffDelBg: "#3d1418",
794
+ monitor: { fleet: "#58a6ff", agents: "#bc8cff", worktree: "#3fb950", phase: "#39c5cf" }
795
+ }),
796
+ "material-ocean": Object.freeze({
797
+ ...baseTheme,
798
+ textPrimary: "#a6accd",
799
+ textSecondary: "#8f93a2",
800
+ textMuted: "#4b526d",
801
+ brandPrimary: "#f78c6c",
802
+ brandAccent: "#c792ea",
803
+ accent: "#89ddff",
804
+ user: "#ffcb6b",
805
+ assistant: "#89ddff",
806
+ tool: "#82aaff",
807
+ success: "#c3e88d",
808
+ warn: "#ffcb6b",
809
+ error: "#f07178",
810
+ borderDefault: "#3b3f51",
811
+ borderSubtle: "#1f2233",
812
+ borderActive: "#89ddff",
813
+ brand: "#c792ea",
814
+ surface: "#0f111a",
815
+ surfaceRaised: "#1a1c25",
816
+ diffAddBg: "#17301f",
817
+ diffDelBg: "#331b21",
818
+ monitor: { fleet: "#89ddff", agents: "#c792ea", worktree: "#c3e88d", phase: "#82aaff" }
819
+ }),
820
+ nightfox: Object.freeze({
821
+ ...baseTheme,
822
+ textPrimary: "#cdcecf",
823
+ textSecondary: "#aeafb0",
824
+ textMuted: "#71839b",
825
+ brandPrimary: "#f4a261",
826
+ brandAccent: "#9d79d6",
827
+ accent: "#719cd6",
828
+ user: "#dbc074",
829
+ assistant: "#63cdcf",
830
+ tool: "#719cd6",
831
+ success: "#81b29a",
832
+ warn: "#dbc074",
833
+ error: "#c94f6d",
834
+ borderDefault: "#39506d",
835
+ borderSubtle: "#29394f",
836
+ borderActive: "#719cd6",
837
+ brand: "#9d79d6",
838
+ surface: "#192330",
839
+ surfaceRaised: "#212e3f",
840
+ diffAddBg: "#1d3025",
841
+ diffDelBg: "#33202a",
842
+ monitor: { fleet: "#719cd6", agents: "#9d79d6", worktree: "#81b29a", phase: "#63cdcf" }
843
+ }),
844
+ oxocarbon: Object.freeze({
845
+ ...baseTheme,
846
+ textPrimary: "#f2f4f8",
847
+ textSecondary: "#dde1e6",
848
+ textMuted: "#82868b",
849
+ brandPrimary: "#ff7eb6",
850
+ brandAccent: "#be95ff",
851
+ accent: "#78a9ff",
852
+ user: "#3ddbd9",
853
+ assistant: "#78a9ff",
854
+ tool: "#3ddbd9",
855
+ success: "#42be65",
856
+ warn: "#ff7eb6",
857
+ error: "#ee5396",
858
+ borderDefault: "#393939",
859
+ borderSubtle: "#262626",
860
+ borderActive: "#be95ff",
861
+ brand: "#be95ff",
862
+ surface: "#161616",
863
+ surfaceRaised: "#262626",
864
+ diffAddBg: "#17301f",
865
+ diffDelBg: "#33182a",
866
+ monitor: { fleet: "#78a9ff", agents: "#be95ff", worktree: "#42be65", phase: "#3ddbd9" }
867
+ }),
868
+ "catppuccin-macchiato": Object.freeze({
869
+ ...baseTheme,
870
+ textPrimary: "#cad3f5",
871
+ textSecondary: "#b8c0e0",
872
+ textMuted: "#6e738d",
873
+ brandPrimary: "#f5a97f",
874
+ brandAccent: "#f5bde6",
875
+ accent: "#8bd5ca",
876
+ user: "#eed49f",
877
+ assistant: "#8aadf4",
878
+ tool: "#8bd5ca",
879
+ success: "#a6da95",
880
+ warn: "#eed49f",
881
+ error: "#ed8796",
882
+ borderDefault: "#5b6078",
883
+ borderSubtle: "#363a4f",
884
+ borderActive: "#eed49f",
885
+ brand: "#c6a0f6",
886
+ surface: "#1e2030",
887
+ surfaceRaised: "#24273a",
888
+ diffAddBg: "#26402f",
889
+ diffDelBg: "#3f242c",
890
+ monitor: { fleet: "#8bd5ca", agents: "#c6a0f6", worktree: "#a6da95", phase: "#8aadf4" }
891
+ }),
892
+ "catppuccin-frappe": Object.freeze({
893
+ ...baseTheme,
894
+ textPrimary: "#c6d0f5",
895
+ textSecondary: "#b5bfe2",
896
+ textMuted: "#737994",
897
+ brandPrimary: "#ef9f76",
898
+ brandAccent: "#f4b8e4",
899
+ accent: "#81c8be",
900
+ user: "#e5c890",
901
+ assistant: "#8caaee",
902
+ tool: "#81c8be",
903
+ success: "#a6d189",
904
+ warn: "#e5c890",
905
+ error: "#e78284",
906
+ borderDefault: "#626880",
907
+ borderSubtle: "#414559",
908
+ borderActive: "#e5c890",
909
+ brand: "#ca9ee6",
910
+ surface: "#292c3c",
911
+ surfaceRaised: "#303446",
912
+ diffAddBg: "#2c4034",
913
+ diffDelBg: "#422a32",
914
+ monitor: { fleet: "#81c8be", agents: "#ca9ee6", worktree: "#a6d189", phase: "#8caaee" }
915
+ }),
916
+ "gruvbox-material": Object.freeze({
917
+ ...baseTheme,
918
+ textPrimary: "#d4be98",
919
+ textSecondary: "#c5b18d",
920
+ textMuted: "#928374",
921
+ brandPrimary: "#e78a4e",
922
+ brandAccent: "#d3869b",
923
+ accent: "#7daea3",
924
+ user: "#d8a657",
925
+ assistant: "#89b482",
926
+ tool: "#7daea3",
927
+ success: "#a9b665",
928
+ warn: "#d8a657",
929
+ error: "#ea6962",
930
+ borderDefault: "#45403d",
931
+ borderSubtle: "#3c3836",
932
+ borderActive: "#e78a4e",
933
+ brand: "#d3869b",
934
+ surface: "#282828",
935
+ surfaceRaised: "#32302f",
936
+ diffAddBg: "#26332a",
937
+ diffDelBg: "#3a2426",
938
+ monitor: { fleet: "#7daea3", agents: "#d3869b", worktree: "#a9b665", phase: "#89b482" }
939
+ }),
940
+ "tokyo-night-storm": Object.freeze({
941
+ ...baseTheme,
942
+ textPrimary: "#c0caf5",
943
+ textSecondary: "#a9b1d6",
944
+ textMuted: "#565f89",
945
+ brandPrimary: "#ff9e64",
946
+ brandAccent: "#bb9af7",
947
+ accent: "#7aa2f7",
948
+ user: "#e0af68",
949
+ assistant: "#7dcfff",
950
+ tool: "#73daca",
951
+ success: "#9ece6a",
952
+ warn: "#e0af68",
953
+ error: "#f7768e",
954
+ borderDefault: "#3b4261",
955
+ borderSubtle: "#292e42",
956
+ borderActive: "#7aa2f7",
957
+ brand: "#bb9af7",
958
+ surface: "#1f2335",
959
+ surfaceRaised: "#24283b",
960
+ diffAddBg: "#20372c",
961
+ diffDelBg: "#3b2331",
962
+ monitor: { fleet: "#7aa2f7", agents: "#bb9af7", worktree: "#9ece6a", phase: "#7dcfff" }
963
+ }),
964
+ "rose-pine-moon": Object.freeze({
965
+ ...baseTheme,
966
+ textPrimary: "#e0def4",
967
+ // Between Rosé Pine's `text` and `subtle` — `subtle` (#908caa) alone falls
968
+ // under WCAG AA once a comment is lifted onto a diff wash.
969
+ textSecondary: "#c4c1d8",
970
+ textMuted: "#6e6a86",
971
+ brandPrimary: "#f6c177",
972
+ brandAccent: "#ea9a97",
973
+ accent: "#9ccfd8",
974
+ user: "#f6c177",
975
+ assistant: "#c4a7e7",
976
+ tool: "#3e8fb0",
977
+ success: "#3e8fb0",
978
+ warn: "#f6c177",
979
+ error: "#eb6f92",
980
+ borderDefault: "#44415a",
981
+ borderSubtle: "#393552",
982
+ borderActive: "#ea9a97",
983
+ brand: "#c4a7e7",
984
+ surface: "#232136",
985
+ surfaceRaised: "#2a273f",
986
+ diffAddBg: "#22403e",
987
+ diffDelBg: "#3a2836",
988
+ monitor: { fleet: "#9ccfd8", agents: "#c4a7e7", worktree: "#3e8fb0", phase: "#9ccfd8" }
989
+ }),
990
+ zenburn: Object.freeze({
991
+ ...baseTheme,
992
+ textPrimary: "#dcdccc",
993
+ textSecondary: "#c0c0a8",
994
+ textMuted: "#8f8f7f",
995
+ brandPrimary: "#dfaf8f",
996
+ brandAccent: "#dc8cc3",
997
+ accent: "#8cd0d3",
998
+ user: "#f0dfaf",
999
+ assistant: "#93e0e3",
1000
+ tool: "#8cd0d3",
1001
+ success: "#7f9f7f",
1002
+ warn: "#f0dfaf",
1003
+ error: "#cc9393",
1004
+ borderDefault: "#5f5f5f",
1005
+ borderSubtle: "#4a4a4a",
1006
+ borderActive: "#f0dfaf",
1007
+ brand: "#dc8cc3",
1008
+ surface: "#3f3f3f",
1009
+ surfaceRaised: "#4f4f4f",
1010
+ // Zenburn's base (#3f3f3f) is the lightest of any preset here, so a wash
1011
+ // blended toward it leaves the muted green/red markers under 3:1. These
1012
+ // sit well below the base on purpose.
1013
+ diffAddBg: "#2b3a2a",
1014
+ diffDelBg: "#42292a",
1015
+ monitor: { fleet: "#8cd0d3", agents: "#dc8cc3", worktree: "#7f9f7f", phase: "#93e0e3" }
1016
+ }),
1017
+ palenight: Object.freeze({
1018
+ ...baseTheme,
1019
+ // Palenight's canonical fg (#a6accd) is dim enough that using it as the
1020
+ // PRIMARY leaves no headroom for a readable secondary; shift both up one
1021
+ // step and keep #a6accd as the secondary.
1022
+ textPrimary: "#babed8",
1023
+ textSecondary: "#a6accd",
1024
+ textMuted: "#676e95",
1025
+ brandPrimary: "#f78c6c",
1026
+ brandAccent: "#c792ea",
1027
+ accent: "#82aaff",
1028
+ user: "#ffcb6b",
1029
+ assistant: "#89ddff",
1030
+ tool: "#82aaff",
1031
+ success: "#c3e88d",
1032
+ warn: "#ffcb6b",
1033
+ error: "#f07178",
1034
+ borderDefault: "#4a4f6a",
1035
+ borderSubtle: "#3a3f58",
1036
+ borderActive: "#82aaff",
1037
+ brand: "#c792ea",
1038
+ surface: "#292d3e",
1039
+ surfaceRaised: "#34324a",
1040
+ diffAddBg: "#2b4033",
1041
+ diffDelBg: "#43293a",
1042
+ monitor: { fleet: "#82aaff", agents: "#c792ea", worktree: "#c3e88d", phase: "#89ddff" }
1043
+ }),
1044
+ horizon: Object.freeze({
1045
+ ...baseTheme,
1046
+ textPrimary: "#d5d8da",
1047
+ textSecondary: "#bbbdbf",
1048
+ textMuted: "#6c6f93",
1049
+ brandPrimary: "#f09383",
1050
+ brandAccent: "#ee64ac",
1051
+ accent: "#26bbd9",
1052
+ user: "#fab795",
1053
+ assistant: "#59e1e3",
1054
+ tool: "#26bbd9",
1055
+ success: "#29d398",
1056
+ warn: "#fab795",
1057
+ error: "#e95678",
1058
+ borderDefault: "#3d3f4c",
1059
+ borderSubtle: "#2e303e",
1060
+ borderActive: "#e95678",
1061
+ brand: "#ee64ac",
1062
+ surface: "#1c1e26",
1063
+ surfaceRaised: "#232530",
1064
+ diffAddBg: "#16362e",
1065
+ diffDelBg: "#3a1f2a",
1066
+ monitor: { fleet: "#26bbd9", agents: "#ee64ac", worktree: "#29d398", phase: "#59e1e3" }
1067
+ }),
1068
+ sonokai: Object.freeze({
1069
+ ...baseTheme,
1070
+ textPrimary: "#e3e1e4",
1071
+ textSecondary: "#c9c6cb",
1072
+ textMuted: "#848089",
1073
+ brandPrimary: "#ef9062",
1074
+ brandAccent: "#ab9df2",
1075
+ accent: "#7accd7",
1076
+ user: "#e5c463",
1077
+ assistant: "#7accd7",
1078
+ tool: "#9ecd6f",
1079
+ success: "#9ecd6f",
1080
+ warn: "#e5c463",
1081
+ error: "#f85e84",
1082
+ borderDefault: "#4b484d",
1083
+ borderSubtle: "#3b383e",
1084
+ borderActive: "#ef9062",
1085
+ brand: "#ab9df2",
1086
+ surface: "#2d2a2e",
1087
+ surfaceRaised: "#37343a",
1088
+ diffAddBg: "#2c3a2b",
1089
+ diffDelBg: "#43262f",
1090
+ monitor: { fleet: "#7accd7", agents: "#ab9df2", worktree: "#9ecd6f", phase: "#7accd7" }
1091
+ }),
1092
+ "edge-dark": Object.freeze({
1093
+ ...baseTheme,
1094
+ textPrimary: "#c5cdd9",
1095
+ textSecondary: "#adb6c2",
1096
+ textMuted: "#7f8490",
1097
+ brandPrimary: "#deb974",
1098
+ brandAccent: "#d38aea",
1099
+ accent: "#6cb6eb",
1100
+ user: "#deb974",
1101
+ assistant: "#5dbbc1",
1102
+ tool: "#6cb6eb",
1103
+ success: "#a0c980",
1104
+ warn: "#deb974",
1105
+ error: "#ec7279",
1106
+ borderDefault: "#464957",
1107
+ borderSubtle: "#363a4d",
1108
+ borderActive: "#6cb6eb",
1109
+ brand: "#d38aea",
1110
+ surface: "#2b2d3a",
1111
+ surfaceRaised: "#333648",
1112
+ diffAddBg: "#29392c",
1113
+ diffDelBg: "#3d2830",
1114
+ monitor: { fleet: "#6cb6eb", agents: "#d38aea", worktree: "#a0c980", phase: "#5dbbc1" }
1115
+ }),
1116
+ moonfly: Object.freeze({
1117
+ ...baseTheme,
1118
+ textPrimary: "#bdbdbd",
1119
+ textSecondary: "#a8a8a8",
1120
+ textMuted: "#767676",
1121
+ brandPrimary: "#de935f",
1122
+ brandAccent: "#cf87e8",
1123
+ accent: "#80a0ff",
1124
+ user: "#e3c78a",
1125
+ assistant: "#79dac8",
1126
+ tool: "#80a0ff",
1127
+ success: "#8cc85f",
1128
+ warn: "#e3c78a",
1129
+ error: "#ff5454",
1130
+ borderDefault: "#3a3a3a",
1131
+ borderSubtle: "#262626",
1132
+ borderActive: "#80a0ff",
1133
+ brand: "#cf87e8",
1134
+ surface: "#080808",
1135
+ surfaceRaised: "#1c1c1c",
1136
+ diffAddBg: "#10301d",
1137
+ diffDelBg: "#331414",
1138
+ monitor: { fleet: "#80a0ff", agents: "#cf87e8", worktree: "#8cc85f", phase: "#79dac8" }
1139
+ }),
1140
+ melange: Object.freeze({
1141
+ ...baseTheme,
1142
+ textPrimary: "#ece1d7",
1143
+ textSecondary: "#c1a78e",
1144
+ textMuted: "#867462",
1145
+ brandPrimary: "#e49b5d",
1146
+ brandAccent: "#cf9bc2",
1147
+ accent: "#a3a9ce",
1148
+ user: "#ebc06d",
1149
+ assistant: "#89b3b6",
1150
+ tool: "#a3a9ce",
1151
+ success: "#85b695",
1152
+ warn: "#ebc06d",
1153
+ error: "#d47766",
1154
+ borderDefault: "#4d4640",
1155
+ borderSubtle: "#3a352f",
1156
+ borderActive: "#e49b5d",
1157
+ brand: "#cf9bc2",
1158
+ surface: "#292522",
1159
+ surfaceRaised: "#34302c",
1160
+ diffAddBg: "#2f3a30",
1161
+ diffDelBg: "#3f2b28",
1162
+ monitor: { fleet: "#a3a9ce", agents: "#cf9bc2", worktree: "#85b695", phase: "#89b3b6" }
1163
+ }),
1164
+ poimandres: Object.freeze({
1165
+ ...baseTheme,
1166
+ // Same shift as palenight — #a6accd becomes the secondary so the lifted
1167
+ // on-wash comment clears AA.
1168
+ textPrimary: "#c4cbe0",
1169
+ textSecondary: "#a6accd",
1170
+ textMuted: "#506477",
1171
+ brandPrimary: "#f087bd",
1172
+ brandAccent: "#a684ff",
1173
+ accent: "#5de4c7",
1174
+ user: "#fffac2",
1175
+ assistant: "#89ddff",
1176
+ tool: "#5de4c7",
1177
+ success: "#5fb3a1",
1178
+ warn: "#fffac2",
1179
+ error: "#d0679d",
1180
+ borderDefault: "#3b4056",
1181
+ borderSubtle: "#252b37",
1182
+ borderActive: "#5de4c7",
1183
+ brand: "#a684ff",
1184
+ surface: "#1b1e28",
1185
+ surfaceRaised: "#252b37",
1186
+ diffAddBg: "#14352f",
1187
+ diffDelBg: "#35202c",
1188
+ monitor: { fleet: "#5de4c7", agents: "#a684ff", worktree: "#5fb3a1", phase: "#89ddff" }
1189
+ }),
1190
+ "vitesse-dark": Object.freeze({
1191
+ ...baseTheme,
1192
+ textPrimary: "#dbd7ca",
1193
+ textSecondary: "#bfbaaa",
1194
+ textMuted: "#758575",
1195
+ brandPrimary: "#dfab5c",
1196
+ brandAccent: "#d3869b",
1197
+ accent: "#6394bf",
1198
+ user: "#e6cc77",
1199
+ assistant: "#5eaab5",
1200
+ tool: "#6394bf",
1201
+ success: "#4d9375",
1202
+ warn: "#e6cc77",
1203
+ error: "#cb7676",
1204
+ borderDefault: "#333333",
1205
+ borderSubtle: "#222222",
1206
+ borderActive: "#dfab5c",
1207
+ brand: "#d3869b",
1208
+ surface: "#121212",
1209
+ surfaceRaised: "#1c1c1c",
1210
+ diffAddBg: "#16301f",
1211
+ diffDelBg: "#331d1d",
1212
+ monitor: { fleet: "#6394bf", agents: "#d3869b", worktree: "#4d9375", phase: "#5eaab5" }
1213
+ }),
1214
+ aura: Object.freeze({
1215
+ ...baseTheme,
1216
+ textPrimary: "#edecee",
1217
+ textSecondary: "#cdccce",
1218
+ textMuted: "#6d6d6d",
1219
+ brandPrimary: "#ffca85",
1220
+ brandAccent: "#f694ff",
1221
+ accent: "#82e2ff",
1222
+ user: "#ffca85",
1223
+ assistant: "#82e2ff",
1224
+ tool: "#a277ff",
1225
+ success: "#61ffca",
1226
+ warn: "#ffca85",
1227
+ error: "#ff6767",
1228
+ borderDefault: "#3b334b",
1229
+ borderSubtle: "#262433",
1230
+ borderActive: "#a277ff",
1231
+ brand: "#f694ff",
1232
+ surface: "#15141b",
1233
+ surfaceRaised: "#1c1b22",
1234
+ diffAddBg: "#14382c",
1235
+ diffDelBg: "#351d23",
1236
+ monitor: { fleet: "#82e2ff", agents: "#a277ff", worktree: "#61ffca", phase: "#f694ff" }
1237
+ }),
1238
+ "dark-plus": Object.freeze({
1239
+ ...baseTheme,
1240
+ textPrimary: "#d4d4d4",
1241
+ textSecondary: "#cccccc",
1242
+ textMuted: "#858585",
1243
+ brandPrimary: "#ce9178",
1244
+ brandAccent: "#c586c0",
1245
+ accent: "#569cd6",
1246
+ user: "#dcdcaa",
1247
+ assistant: "#9cdcfe",
1248
+ tool: "#4ec9b0",
1249
+ success: "#89d185",
1250
+ warn: "#cca700",
1251
+ error: "#f44747",
1252
+ borderDefault: "#3c3c3c",
1253
+ borderSubtle: "#2d2d30",
1254
+ borderActive: "#569cd6",
1255
+ brand: "#c586c0",
1256
+ surface: "#1e1e1e",
1257
+ surfaceRaised: "#252526",
1258
+ diffAddBg: "#16351f",
1259
+ diffDelBg: "#3a1d20",
1260
+ monitor: { fleet: "#569cd6", agents: "#c586c0", worktree: "#89d185", phase: "#4ec9b0" },
1261
+ // The one preset that genuinely disagrees with the derived defaults:
1262
+ // VS Code colors strings orange and comments green, the inverse of the
1263
+ // usual green-string / grey-comment convention the defaults assume.
1264
+ // `commentOnWash` is a lifted green rather than the default
1265
+ // `textSecondary`, so the comment stays green on a diff wash instead of
1266
+ // switching to grey mid-file.
1267
+ syntax: {
1268
+ string: "#ce9178",
1269
+ comment: "#6a9955",
1270
+ commentOnWash: "#9fc98b"
1271
+ }
730
1272
  })
731
1273
  });
732
1274
  var THEME_OPTIONS = [
@@ -796,6 +1338,106 @@ var THEME_OPTIONS = [
796
1338
  id: "synthwave",
797
1339
  name: "Synthwave '84",
798
1340
  description: "Hot pink + neon cyan on deep purple \u2014 80s retro glow"
1341
+ },
1342
+ {
1343
+ id: "github-dark",
1344
+ name: "GitHub Dark",
1345
+ description: "GitHub's default dark \u2014 crisp blue on near-black"
1346
+ },
1347
+ {
1348
+ id: "material-ocean",
1349
+ name: "Material Ocean",
1350
+ description: "Deepest Material variant \u2014 ink blue with pastel accents"
1351
+ },
1352
+ {
1353
+ id: "nightfox",
1354
+ name: "Nightfox",
1355
+ description: "Balanced slate blue with muted sage and rose"
1356
+ },
1357
+ {
1358
+ id: "oxocarbon",
1359
+ name: "Oxocarbon",
1360
+ description: "IBM Carbon-derived \u2014 neutral greys, electric blue & pink"
1361
+ },
1362
+ {
1363
+ id: "catppuccin-macchiato",
1364
+ name: "Catppuccin Macchiato",
1365
+ description: "Warmer, one shade lighter than Mocha"
1366
+ },
1367
+ {
1368
+ id: "catppuccin-frappe",
1369
+ name: "Catppuccin Frapp\xE9",
1370
+ description: "The lightest Catppuccin dark \u2014 gentle midday contrast"
1371
+ },
1372
+ {
1373
+ id: "gruvbox-material",
1374
+ name: "Gruvbox Material",
1375
+ description: "Softened Gruvbox \u2014 same warmth, lower eye strain"
1376
+ },
1377
+ {
1378
+ id: "tokyo-night-storm",
1379
+ name: "Tokyo Night Storm",
1380
+ description: "Tokyo Night on a lifted blue-grey base"
1381
+ },
1382
+ {
1383
+ id: "rose-pine-moon",
1384
+ name: "Ros\xE9 Pine Moon",
1385
+ description: "Ros\xE9 Pine at dusk \u2014 deeper base, same soft accents"
1386
+ },
1387
+ {
1388
+ id: "zenburn",
1389
+ name: "Zenburn",
1390
+ description: "The classic low-contrast grey \u2014 desaturated and calm"
1391
+ },
1392
+ {
1393
+ id: "palenight",
1394
+ name: "Palenight",
1395
+ description: "Material Palenight \u2014 indigo base, candy accents"
1396
+ },
1397
+ {
1398
+ id: "horizon",
1399
+ name: "Horizon",
1400
+ description: "Warm coral and mint on charcoal \u2014 sunset gradient"
1401
+ },
1402
+ {
1403
+ id: "sonokai",
1404
+ name: "Sonokai",
1405
+ description: "Monokai Pro descendant \u2014 punchy on warm graphite"
1406
+ },
1407
+ {
1408
+ id: "edge-dark",
1409
+ name: "Edge Dark",
1410
+ description: "Clean, evenly-weighted palette on desaturated navy"
1411
+ },
1412
+ {
1413
+ id: "moonfly",
1414
+ name: "Moonfly",
1415
+ description: "Near-black base with high-chroma accents \u2014 maximum contrast"
1416
+ },
1417
+ {
1418
+ id: "melange",
1419
+ name: "Melange",
1420
+ description: "Warm sepia and clay \u2014 the least blue dark theme here"
1421
+ },
1422
+ {
1423
+ id: "poimandres",
1424
+ name: "Poimandres",
1425
+ description: "Teal-forward, low-saturation \u2014 mint on deep indigo"
1426
+ },
1427
+ {
1428
+ id: "vitesse-dark",
1429
+ name: "Vitesse Dark",
1430
+ description: "Anthony Fu's minimal palette \u2014 muted, print-like"
1431
+ },
1432
+ {
1433
+ id: "aura",
1434
+ name: "Aura Dark",
1435
+ description: "Vivid purple and spring green on near-black violet"
1436
+ },
1437
+ {
1438
+ id: "dark-plus",
1439
+ name: "VS Code Dark+",
1440
+ description: "Visual Studio Code's default \u2014 familiar blue/orange/teal"
799
1441
  }
800
1442
  ];
801
1443
 
@@ -10649,20 +11291,20 @@ import React2 from "react";
10649
11291
 
10650
11292
  // src/highlight.ts
10651
11293
  var C = {
10652
- keyword: "magenta",
10653
- string: "green",
10654
- comment: "gray",
10655
- number: "yellow",
10656
- literal: "yellow",
11294
+ keyword: "syntax.keyword",
11295
+ string: "syntax.string",
11296
+ comment: "syntax.comment",
11297
+ number: "syntax.number",
11298
+ literal: "syntax.literal",
10657
11299
  // true/false/null
10658
- property: "cyan",
10659
- variable: "cyan",
10660
- command: "cyan",
10661
- flag: "yellow",
10662
- decorator: "magenta",
10663
- diffAdd: "green",
10664
- diffDel: "red",
10665
- diffMeta: "cyan"
11300
+ property: "syntax.property",
11301
+ variable: "syntax.variable",
11302
+ command: "syntax.command",
11303
+ flag: "syntax.flag",
11304
+ decorator: "syntax.decorator",
11305
+ diffAdd: "syntax.diffAdd",
11306
+ diffDel: "syntax.diffDel",
11307
+ diffMeta: "syntax.diffMeta"
10666
11308
  };
10667
11309
  var TS_KEYWORDS = /* @__PURE__ */ new Set([
10668
11310
  "const",
@@ -13407,8 +14049,8 @@ function formatDiffStats(added, removed) {
13407
14049
  function applyWashTokens(tokens, onWash) {
13408
14050
  if (!onWash) return tokens;
13409
14051
  return tokens.map((t) => {
13410
- if (t.color !== "gray") return t;
13411
- return { ...t, color: "grayBright", dim: false };
14052
+ if (t.color !== "syntax.comment") return t;
14053
+ return { ...t, color: "syntax.commentOnWash", dim: false };
13412
14054
  });
13413
14055
  }
13414
14056
  function renderTokens(tokens, onWash = false) {
@@ -23557,7 +24199,7 @@ function ThemePicker({
23557
24199
  const isSelected = i === selected;
23558
24200
  return /* @__PURE__ */ jsxs80(Text, { inverse: isSelected, ...isSelected ? { color: "cyan" } : {}, children: [
23559
24201
  isSelected ? "\u203A " : " ",
23560
- /* @__PURE__ */ jsx83(Text, { bold: true, children: opt.name.padEnd(18) }),
24202
+ /* @__PURE__ */ jsx83(Text, { bold: true, children: opt.name.padEnd(21) }),
23561
24203
  /* @__PURE__ */ jsx83(Text, { dimColor: true, children: opt.description }),
23562
24204
  isActive ? /* @__PURE__ */ jsx83(Text, { color: "green", children: " [active]" }) : null
23563
24205
  ] }, opt.id);
package/dist/theme.d.ts CHANGED
@@ -10,6 +10,8 @@ export declare const pastel: Readonly<{
10
10
  readonly white: '#cdd6f4';
11
11
  readonly gray: '#7f849c';
12
12
  readonly grey: '#7f849c';
13
+ readonly grayBright: '#a6adc8';
14
+ readonly greyBright: '#a6adc8';
13
15
  readonly blackBright: '#585b70';
14
16
  readonly redBright: '#eba0ac';
15
17
  readonly greenBright: '#b8e8b0';
@@ -40,6 +42,8 @@ export declare const catppuccin: Readonly<{
40
42
  readonly white: '#cdd6f4';
41
43
  readonly gray: '#7f849c';
42
44
  readonly grey: '#7f849c';
45
+ readonly grayBright: '#a6adc8';
46
+ readonly greyBright: '#a6adc8';
43
47
  readonly blackBright: '#585b70';
44
48
  readonly redBright: '#eba0ac';
45
49
  readonly greenBright: '#b8e8b0';
@@ -55,11 +59,34 @@ export declare const catppuccin: Readonly<{
55
59
  readonly subtext0: '#a6adc8';
56
60
  readonly flamingo: '#f2cdcd';
57
61
  }>;
62
+ /** The distinct things `highlightLine` can color. */
63
+ export type SyntaxRole = 'keyword' | 'string' | 'comment' | 'commentOnWash' | 'number' | 'literal' | 'property' | 'variable' | 'command' | 'flag' | 'decorator' | 'diffAdd' | 'diffDel' | 'diffMeta';
64
+ export type SyntaxPalette = Record<SyntaxRole, string>;
58
65
  /**
59
- * Resolve a color value to its pastel equivalent. Known ANSI names map to the
60
- * {@link pastel} hex; hex/rgb strings and unknown values (e.g. Ink's `'dim'`)
61
- * pass through unchanged. `undefined` stays `undefined` so callers can spread
62
- * it without forcing a color.
66
+ * Resolve one syntax role against a palette. A preset may pin any role
67
+ * explicitly via its optional `syntax` field (e.g. a scheme whose strings are
68
+ * orange rather than green); otherwise the role borrows the token named in
69
+ * {@link SYNTAX_TOKEN}. O(1) and allocation-free — this runs once per token
70
+ * per rendered line.
71
+ */
72
+ export declare function resolveSyntaxColor(role: SyntaxRole, palette?: Theme): string;
73
+ /** Materialize the full syntax palette for a theme. Used by tests and tooling. */
74
+ export declare function resolveSyntaxPalette(palette?: Theme): SyntaxPalette;
75
+ /**
76
+ * Resolve a color value to a concrete color.
77
+ *
78
+ * Three cases, in order:
79
+ * - `syntax.<role>` resolves against the ACTIVE theme, so highlighting
80
+ * follows `/theme` (see {@link resolveSyntaxColor}).
81
+ * - A known ANSI name maps to its {@link pastel} hex. This map is frozen and
82
+ * theme-independent by design — it is the floor for the ~177 call sites
83
+ * that still pass bare names like `color="red"`.
84
+ * - Anything else (hex, rgb, Ink's `'dim'`, an unknown name) passes through.
85
+ *
86
+ * `undefined` stays `undefined` so callers can spread it without forcing a
87
+ * color. NOTE: an unknown name that reaches Ink is silently DROPPED by chalk —
88
+ * the token renders with no color at all. Never emit a name that isn't
89
+ * resolvable here.
63
90
  */
64
91
  export declare function softColor(color?: string): string | undefined;
65
92
  /**
@@ -154,6 +181,14 @@ export interface Theme {
154
181
  */
155
182
  diffAddBg: string;
156
183
  diffDelBg: string;
184
+ /**
185
+ * Per-preset syntax-highlight overrides. OPTIONAL and normally omitted —
186
+ * every role has a sensible default derived from the tokens above (see
187
+ * `SYNTAX_TOKEN`), so a preset only pins a role when its scheme genuinely
188
+ * disagrees. Example: VS Code Dark+ colors strings orange, not green, so
189
+ * `dark-plus` pins `string`.
190
+ */
191
+ syntax?: Partial<SyntaxPalette> | undefined;
157
192
  }
158
193
  export declare const theme: Theme;
159
194
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tui",
3
- "version": "0.305.0",
3
+ "version": "0.305.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack terminal UI: Ink-based interactive renderer with history, pickers, and slash menus.",
6
6
  "repository": {
@@ -29,12 +29,12 @@
29
29
  "ink": "7.1.1",
30
30
  "react": "^19.2.8",
31
31
  "string-width": "8.2.2",
32
- "@wrongstack/core": "0.305.0",
33
- "@wrongstack/kanban": "0.305.0",
34
- "@wrongstack/sage": "0.305.0",
35
- "@wrongstack/runtime": "0.305.0",
36
- "@wrongstack/sdd": "0.305.0",
37
- "@wrongstack/tools": "0.305.0"
32
+ "@wrongstack/core": "0.305.1",
33
+ "@wrongstack/runtime": "0.305.1",
34
+ "@wrongstack/kanban": "0.305.1",
35
+ "@wrongstack/sdd": "0.305.1",
36
+ "@wrongstack/sage": "0.305.1",
37
+ "@wrongstack/tools": "0.305.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@testing-library/dom": "^10.4.1",