envprism 0.1.0-dev.10-1179a3d → 0.1.0-dev.12-c99a64a

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.
@@ -1,35 +1,8 @@
1
- import { RGBA, BoxRenderable, ScrollBoxRenderable, TextRenderable, createCliRenderer } from "@opentui/core";
2
- import { m as maskValue, r as rebuildKvLine, s as serializeEnv, i as isSecretKey } from "./mask-Bv9W6Ei8.mjs";
1
+ import { BoxRenderable, ScrollBoxRenderable, TextRenderable, RGBA, createCliRenderer } from "@opentui/core";
2
+ import { j as matchesFilter, b as buildMatrix, f as findKvEntry, t as truncate, h as formatValue, n as resolveThemeHex, a as DEFAULT_THEME_HEX, D as DEFAULT_CONFIG, k as resolveHeuristics } from "./matrix-C1pErf9Z.mjs";
3
+ import { r as rebuildKvLine, s as serializeEnv } from "./serialize-D_ZNIgRa.mjs";
3
4
  import { basename, join, dirname } from "pathe";
4
- import { b as buildMatrix } from "./matrix-nBq_GY2v.mjs";
5
5
  import { writeFile } from "node:fs/promises";
6
- const PLACEHOLDER_RE = /^(todo|fixme|changeme|placeholder|tbd|x{3,}|your[_-]?(secret|key|token|password|api[_-]?key)(_here)?|replace[_-]?me)$/i;
7
- function isPlaceholderValue(value) {
8
- const v = value.trim();
9
- if (v.length === 0) return false;
10
- return PLACEHOLDER_RE.test(v);
11
- }
12
- function formatValue(value, secret) {
13
- if (value === void 0) return "";
14
- if (secret) return maskValue(value);
15
- return value;
16
- }
17
- function matchesFilter(key, filter) {
18
- if (!filter) return true;
19
- return key.toLowerCase().includes(filter.toLowerCase());
20
- }
21
- function truncate(text, width) {
22
- if (width <= 0) return "";
23
- if (text.length <= width) return text;
24
- if (width <= 1) return "…";
25
- return `${text.slice(0, width - 1)}…`;
26
- }
27
- function findKvEntry(file, key) {
28
- for (const e of file.entries) {
29
- if (e.kind === "kv" && e.key === key) return e;
30
- }
31
- return void 0;
32
- }
33
6
  const SECTION_COLLAPSE_KEY = "__other__";
34
7
  function prefixSection(key) {
35
8
  const idx = key.indexOf("_");
@@ -135,8 +108,6 @@ function appendKv(file, key, value) {
135
108
  file.trailingNewline = true;
136
109
  return entry;
137
110
  }
138
- const UNDO_LIMIT = 50;
139
- const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
140
111
  function cellKey(key, file) {
141
112
  return `${key}|${file.path}`;
142
113
  }
@@ -145,7 +116,7 @@ function markModified(ctx, key, file) {
145
116
  }
146
117
  function pushUndo(ctx, entry) {
147
118
  ctx.state.undo.push(entry);
148
- if (ctx.state.undo.length > UNDO_LIMIT) ctx.state.undo.shift();
119
+ if (ctx.state.undo.length > ctx.config.tui.undoLimit) ctx.state.undo.shift();
149
120
  }
150
121
  function focusedKey(ctx) {
151
122
  const item = ctx.state.visibleItems[ctx.state.rowIdx];
@@ -344,6 +315,7 @@ async function saveDirty(ctx) {
344
315
  }
345
316
  ctx.refresh();
346
317
  }
318
+ const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
347
319
  function openPrompt(ctx, prompt, value = "") {
348
320
  const { state } = ctx;
349
321
  state.prompt = prompt;
@@ -723,34 +695,7 @@ function createOnKey(ctx, cleanup) {
723
695
  handleBrowseKey(ctx, key, cleanup);
724
696
  };
725
697
  }
726
- const COLORS = {
727
- fg: RGBA.fromHex("#cccccc"),
728
- fgDim: RGBA.fromHex("#666666"),
729
- fgHeader: RGBA.fromHex("#ffffff"),
730
- // accent — base file + section names (blue/purple, single accent for
731
- // navigational anchors).
732
- fgBase: RGBA.fromHex("#82aaff"),
733
- fgSection: RGBA.fromHex("#82aaff"),
734
- // drift/extra/placeholder — same yellow. They describe disagreement with
735
- // the base file, all one semantic.
736
- differs: RGBA.fromHex("#ffd866"),
737
- extra: RGBA.fromHex("#ffd866"),
738
- placeholder: RGBA.fromHex("#ffd866"),
739
- // user-made changes (modified cells, dirty files, unsaved counter) — green.
740
- // Different colour from drift on purpose: "I just touched this" is a
741
- // different signal from "this disagrees with the base".
742
- modified: RGBA.fromHex("#7fce6a"),
743
- fgDirty: RGBA.fromHex("#7fce6a"),
744
- // problem — value-is-missing red. Reserved exclusively for missing.
745
- missing: RGBA.fromHex("#ff6b6b"),
746
- focusBg: RGBA.fromHex("#3a3f4b")
747
- };
748
- const KEY_COL_WIDTH = 22;
749
- const VALUE_COL_MIN = 18;
750
- const SIDEBAR_WIDTH = 30;
751
- const ROW_GAP = 0;
752
- const CELL_PAD_X = 1;
753
- function buildLayout(renderer) {
698
+ function buildLayout(renderer, theme, layout) {
754
699
  const root = new BoxRenderable(renderer, {
755
700
  id: "root",
756
701
  flexDirection: "column",
@@ -770,7 +715,7 @@ function buildLayout(renderer) {
770
715
  borderStyle: "rounded",
771
716
  title: "",
772
717
  flexDirection: "column",
773
- width: SIDEBAR_WIDTH,
718
+ width: layout.SIDEBAR_WIDTH,
774
719
  flexShrink: 0,
775
720
  paddingX: 1
776
721
  });
@@ -805,7 +750,7 @@ function buildLayout(renderer) {
805
750
  // Reserve a column on the right so the vertical scrollbar doesn't sit on
806
751
  // top of cell content, and a row at the bottom for the horizontal one.
807
752
  viewportOptions: { paddingRight: 1, paddingBottom: 1 },
808
- contentOptions: { flexDirection: "column", rowGap: ROW_GAP }
753
+ contentOptions: { flexDirection: "column", rowGap: layout.ROW_GAP }
809
754
  });
810
755
  matrixBox.add(scrollBox);
811
756
  const footer = new BoxRenderable(renderer, {
@@ -832,7 +777,7 @@ function buildLayout(renderer) {
832
777
  const status = new TextRenderable(renderer, {
833
778
  id: "status",
834
779
  content: "",
835
- fg: COLORS.fgDim,
780
+ fg: theme.fgDim,
836
781
  wrapMode: "none",
837
782
  height: 1
838
783
  });
@@ -857,14 +802,14 @@ function buildLayout(renderer) {
857
802
  const filterField = new TextRenderable(renderer, {
858
803
  id: "filter-field",
859
804
  content: "",
860
- fg: COLORS.fg,
805
+ fg: theme.fg,
861
806
  height: 1,
862
807
  wrapMode: "none"
863
808
  });
864
809
  const filterStatus = new TextRenderable(renderer, {
865
810
  id: "filter-status",
866
811
  content: "",
867
- fg: COLORS.fgDim,
812
+ fg: theme.fgDim,
868
813
  height: 1,
869
814
  marginTop: 1,
870
815
  wrapMode: "none"
@@ -872,7 +817,7 @@ function buildLayout(renderer) {
872
817
  const filterHint = new TextRenderable(renderer, {
873
818
  id: "filter-hint",
874
819
  content: "Enter · keep filter Esc · clear & close",
875
- fg: COLORS.fg,
820
+ fg: theme.fg,
876
821
  height: 1,
877
822
  marginTop: 1,
878
823
  wrapMode: "none"
@@ -906,7 +851,7 @@ function buildLayout(renderer) {
906
851
  const promptHint = new TextRenderable(renderer, {
907
852
  id: "prompt-hint",
908
853
  content: "",
909
- fg: COLORS.fg,
854
+ fg: theme.fg,
910
855
  height: 1,
911
856
  marginTop: 2,
912
857
  wrapMode: "none"
@@ -984,14 +929,14 @@ function removeAllChildren(node) {
984
929
  const ids = node.getChildren().map((c) => c.id);
985
930
  for (const id of ids) node.remove(id);
986
931
  }
987
- function bindings(specs) {
932
+ function bindings(specs, theme) {
988
933
  const out = [];
989
934
  specs.forEach((spec, i) => {
990
- if (i > 0) out.push({ text: " · ", fg: COLORS.fgDim });
991
- out.push({ text: "[", fg: COLORS.fgDim });
992
- out.push({ text: spec.key, fg: COLORS.fg });
993
- out.push({ text: "] ", fg: COLORS.fgDim });
994
- out.push({ text: spec.label, fg: COLORS.fg });
935
+ if (i > 0) out.push({ text: " · ", fg: theme.fgDim });
936
+ out.push({ text: "[", fg: theme.fgDim });
937
+ out.push({ text: spec.key, fg: theme.fg });
938
+ out.push({ text: "] ", fg: theme.fgDim });
939
+ out.push({ text: spec.label, fg: theme.fg });
995
940
  });
996
941
  return out;
997
942
  }
@@ -1013,67 +958,77 @@ function refreshFooter(ctx) {
1013
958
  const {
1014
959
  el: { hintA, hintB, status },
1015
960
  renderer,
1016
- state
961
+ state,
962
+ theme
1017
963
  } = ctx;
1018
964
  const dirty = state.dirty.size;
1019
965
  const dirtyTail = dirty > 0 ? [
1020
- { text: " ", fg: COLORS.fgDim },
1021
- { text: "●", fg: COLORS.modified },
1022
- { text: ` ${dirty} unsaved`, fg: COLORS.fg }
966
+ { text: " ", fg: theme.fgDim },
967
+ { text: "●", fg: theme.modified },
968
+ { text: ` ${dirty} unsaved`, fg: theme.fg }
1023
969
  ] : [];
1024
970
  if (state.mode === "filter") {
1025
971
  renderHintBox(hintA, renderer, [
1026
- ...bindings([
1027
- { key: "Enter", label: "keep filter" },
1028
- { key: "Esc", label: "clear" }
1029
- ]),
972
+ ...bindings(
973
+ [
974
+ { key: "Enter", label: "keep filter" },
975
+ { key: "Esc", label: "clear" }
976
+ ],
977
+ theme
978
+ ),
1030
979
  ...dirtyTail
1031
980
  ]);
1032
- renderHintBox(hintB, renderer, [{ text: " Filter:", fg: COLORS.fgDim }]);
981
+ renderHintBox(hintB, renderer, [{ text: " Filter:", fg: theme.fgDim }]);
1033
982
  } else if (state.mode === "prompt") {
1034
983
  renderHintBox(hintA, renderer, []);
1035
984
  renderHintBox(hintB, renderer, []);
1036
985
  } else if (state.pane === "sidebar") {
1037
986
  renderHintBox(hintA, renderer, [
1038
- ...bindings([
1039
- { key: "↑↓", label: "move" },
1040
- { key: "Space", label: "toggle" },
1041
- { key: "b", label: "set base" },
1042
- { key: "Tab/→", label: "matrix" },
1043
- { key: "^S", label: "save" },
1044
- { key: "?", label: "help" },
1045
- { key: "q", label: "quit" }
1046
- ]),
987
+ ...bindings(
988
+ [
989
+ { key: "↑↓", label: "move" },
990
+ { key: "Space", label: "toggle" },
991
+ { key: "b", label: "set base" },
992
+ { key: "Tab/→", label: "matrix" },
993
+ { key: "^S", label: "save" },
994
+ { key: "?", label: "help" },
995
+ { key: "q", label: "quit" }
996
+ ],
997
+ theme
998
+ ),
1047
999
  ...dirtyTail
1048
1000
  ]);
1049
- renderHintBox(hintB, renderer, [{ text: "Files pane", fg: COLORS.fgDim }]);
1001
+ renderHintBox(hintB, renderer, [{ text: "Files pane", fg: theme.fgDim }]);
1050
1002
  } else {
1051
1003
  renderHintBox(hintA, renderer, [
1052
- ...bindings([
1053
- { key: "↑↓←→", label: "move" },
1054
- { key: "Tab", label: "files" },
1055
- { key: "e", label: "edit" },
1056
- { key: "a", label: "add var" },
1057
- { key: "d", label: "del var" },
1058
- { key: "n", label: "new file" },
1059
- { key: "=", label: "sync to all" },
1060
- { key: "c", label: "collapse" },
1061
- { key: "^T", label: "secrets" },
1062
- { key: "^Z", label: "undo" },
1063
- { key: "^S", label: "save" },
1064
- { key: "/", label: "filter" },
1065
- { key: "?/ß", label: "help" },
1066
- { key: "q", label: "quit" }
1067
- ]),
1004
+ ...bindings(
1005
+ [
1006
+ { key: "↑↓←→", label: "move" },
1007
+ { key: "Tab", label: "files" },
1008
+ { key: "e", label: "edit" },
1009
+ { key: "a", label: "add var" },
1010
+ { key: "d", label: "del var" },
1011
+ { key: "n", label: "new file" },
1012
+ { key: "=", label: "sync to all" },
1013
+ { key: "c", label: "collapse" },
1014
+ { key: "^T", label: "secrets" },
1015
+ { key: "^Z", label: "undo" },
1016
+ { key: "^S", label: "save" },
1017
+ { key: "/", label: "filter" },
1018
+ { key: "?/ß", label: "help" },
1019
+ { key: "q", label: "quit" }
1020
+ ],
1021
+ theme
1022
+ ),
1068
1023
  ...dirtyTail
1069
1024
  ]);
1070
1025
  renderHintBox(hintB, renderer, [
1071
- { text: "view: ", fg: COLORS.fgDim },
1072
- { text: state.driftOnly ? "drift" : "all", fg: COLORS.fg },
1073
- { text: " · group: ", fg: COLORS.fgDim },
1074
- { text: state.grouping, fg: COLORS.fg },
1075
- { text: " · secrets: ", fg: COLORS.fgDim },
1076
- { text: state.showSecrets ? "shown" : "masked", fg: COLORS.fg }
1026
+ { text: "view: ", fg: theme.fgDim },
1027
+ { text: state.driftOnly ? "drift" : "all", fg: theme.fg },
1028
+ { text: " · group: ", fg: theme.fgDim },
1029
+ { text: state.grouping, fg: theme.fg },
1030
+ { text: " · secrets: ", fg: theme.fgDim },
1031
+ { text: state.showSecrets ? "shown" : "masked", fg: theme.fg }
1077
1032
  ]);
1078
1033
  }
1079
1034
  status.content = state.message ?? "";
@@ -1177,40 +1132,40 @@ function buildHelpLines() {
1177
1132
  }
1178
1133
  ];
1179
1134
  }
1180
- function buildValueCell(cell, secret, width, focused, modified) {
1181
- const bg = focused ? COLORS.focusBg : void 0;
1182
- const trailing = modified ? { char: "●", fg: COLORS.modified } : void 0;
1135
+ function buildValueCell(cell, secret, width, focused, modified, theme, isPlaceholder) {
1136
+ const bg = focused ? theme.focusBg : void 0;
1137
+ const trailing = modified ? { char: "●", fg: theme.modified } : void 0;
1183
1138
  if (cell.state === "missing") {
1184
1139
  return {
1185
1140
  text: "missing",
1186
- fg: COLORS.fgDim,
1141
+ fg: theme.fgDim,
1187
1142
  width,
1188
1143
  bg,
1189
- icon: { char: "✗", fg: COLORS.missing },
1144
+ icon: { char: "✗", fg: theme.missing },
1190
1145
  trailing
1191
1146
  };
1192
1147
  }
1193
1148
  const value = cell.value ?? "";
1194
- if (value !== "" && isPlaceholderValue(value)) {
1149
+ if (value !== "" && isPlaceholder(value)) {
1195
1150
  return {
1196
1151
  text: value,
1197
- fg: COLORS.fg,
1152
+ fg: theme.fg,
1198
1153
  width,
1199
1154
  bg,
1200
- icon: { char: "⚠", fg: COLORS.placeholder },
1155
+ icon: { char: "⚠", fg: theme.placeholder },
1201
1156
  trailing
1202
1157
  };
1203
1158
  }
1204
1159
  const isEmpty = value === "" && !secret;
1205
1160
  const displayText = isEmpty ? "(empty)" : formatValue(value, secret);
1206
- const displayFg = isEmpty ? COLORS.fgDim : COLORS.fg;
1161
+ const displayFg = isEmpty ? theme.fgDim : theme.fg;
1207
1162
  if (cell.state === "differs") {
1208
1163
  return {
1209
1164
  text: displayText,
1210
1165
  fg: displayFg,
1211
1166
  width,
1212
1167
  bg,
1213
- icon: { char: "≠", fg: COLORS.differs },
1168
+ icon: { char: "≠", fg: theme.differs },
1214
1169
  trailing
1215
1170
  };
1216
1171
  }
@@ -1220,13 +1175,13 @@ function buildValueCell(cell, secret, width, focused, modified) {
1220
1175
  fg: displayFg,
1221
1176
  width,
1222
1177
  bg,
1223
- icon: { char: "★", fg: COLORS.extra },
1178
+ icon: { char: "★", fg: theme.extra },
1224
1179
  trailing
1225
1180
  };
1226
1181
  }
1227
1182
  return { text: displayText, fg: displayFg, width, bg, trailing };
1228
1183
  }
1229
- function buildHelpRow(renderer, id, line) {
1184
+ function buildHelpRow(renderer, id, line, theme) {
1230
1185
  const row = new BoxRenderable(renderer, {
1231
1186
  id,
1232
1187
  flexDirection: "row",
@@ -1238,7 +1193,7 @@ function buildHelpRow(renderer, id, line) {
1238
1193
  new TextRenderable(renderer, {
1239
1194
  id: `${id}-t`,
1240
1195
  content: line.text,
1241
- fg: COLORS.fgSection,
1196
+ fg: theme.fgSection,
1242
1197
  wrapMode: "none",
1243
1198
  height: 1
1244
1199
  })
@@ -1248,7 +1203,7 @@ function buildHelpRow(renderer, id, line) {
1248
1203
  new TextRenderable(renderer, {
1249
1204
  id: `${id}-t`,
1250
1205
  content: line.text,
1251
- fg: COLORS.fg,
1206
+ fg: theme.fg,
1252
1207
  wrapMode: "none",
1253
1208
  height: 1
1254
1209
  })
@@ -1267,7 +1222,7 @@ function buildHelpRow(renderer, id, line) {
1267
1222
  new TextRenderable(renderer, {
1268
1223
  id: `${id}-desc`,
1269
1224
  content: line.description,
1270
- fg: COLORS.fgDim,
1225
+ fg: theme.fgDim,
1271
1226
  wrapMode: "none",
1272
1227
  height: 1
1273
1228
  })
@@ -1275,31 +1230,31 @@ function buildHelpRow(renderer, id, line) {
1275
1230
  }
1276
1231
  return row;
1277
1232
  }
1278
- function buildSectionDivider(renderer, id, name, width, meta) {
1233
+ function buildSectionDivider(renderer, id, name, width, meta, theme) {
1279
1234
  const baseName = name ?? "(other)";
1280
1235
  const indicator = meta.collapsed ? "▸" : "▾";
1281
1236
  const segs = [
1282
- { text: ` ${indicator} `, fg: COLORS.fgDim },
1283
- { text: baseName, fg: COLORS.fg },
1284
- { text: " ", fg: COLORS.fgDim }
1237
+ { text: ` ${indicator} `, fg: theme.fgDim },
1238
+ { text: baseName, fg: theme.fg },
1239
+ { text: " ", fg: theme.fgDim }
1285
1240
  ];
1286
1241
  if (meta.missing > 0) {
1287
- segs.push({ text: "✗ ", fg: COLORS.missing });
1288
- segs.push({ text: `${meta.missing}`, fg: COLORS.missing });
1289
- segs.push({ text: " missing ", fg: COLORS.fg });
1242
+ segs.push({ text: "✗ ", fg: theme.missing });
1243
+ segs.push({ text: `${meta.missing}`, fg: theme.missing });
1244
+ segs.push({ text: " missing ", fg: theme.fg });
1290
1245
  }
1291
1246
  if (meta.drift > 0) {
1292
- segs.push({ text: "≠ ", fg: COLORS.differs });
1293
- segs.push({ text: `${meta.drift}`, fg: COLORS.differs });
1294
- segs.push({ text: "/", fg: COLORS.fgDim });
1295
- segs.push({ text: `${meta.total}`, fg: COLORS.differs });
1296
- segs.push({ text: " drift ", fg: COLORS.fg });
1247
+ segs.push({ text: "≠ ", fg: theme.differs });
1248
+ segs.push({ text: `${meta.drift}`, fg: theme.differs });
1249
+ segs.push({ text: "/", fg: theme.fgDim });
1250
+ segs.push({ text: `${meta.total}`, fg: theme.differs });
1251
+ segs.push({ text: " drift ", fg: theme.fg });
1297
1252
  }
1298
1253
  if (meta.missing === 0 && meta.drift === 0) {
1299
- segs.push({ text: `${meta.total}`, fg: COLORS.fgDim });
1300
- segs.push({ text: " keys ", fg: COLORS.fg });
1254
+ segs.push({ text: `${meta.total}`, fg: theme.fgDim });
1255
+ segs.push({ text: " keys ", fg: theme.fg });
1301
1256
  }
1302
- segs.push({ text: " ", fg: COLORS.fgDim });
1257
+ segs.push({ text: " ", fg: theme.fgDim });
1303
1258
  const labelLength = segs.reduce((sum, s) => sum + s.text.length, 0);
1304
1259
  const rule = "─";
1305
1260
  const visible = Math.max(0, width - 2);
@@ -1311,13 +1266,13 @@ function buildSectionDivider(renderer, id, name, width, meta) {
1311
1266
  flexShrink: 0,
1312
1267
  height: 1,
1313
1268
  paddingX: 1,
1314
- ...meta.focused ? { backgroundColor: COLORS.focusBg } : {}
1269
+ ...meta.focused ? { backgroundColor: theme.focusBg } : {}
1315
1270
  });
1316
1271
  box.add(
1317
1272
  new TextRenderable(renderer, {
1318
1273
  id: `${id}-lead`,
1319
1274
  content: rule.repeat(beforeLen),
1320
- fg: COLORS.fgDim,
1275
+ fg: theme.fgDim,
1321
1276
  height: 1,
1322
1277
  wrapMode: "none"
1323
1278
  })
@@ -1337,14 +1292,14 @@ function buildSectionDivider(renderer, id, name, width, meta) {
1337
1292
  new TextRenderable(renderer, {
1338
1293
  id: `${id}-trail`,
1339
1294
  content: rule.repeat(afterLen),
1340
- fg: COLORS.fgDim,
1295
+ fg: theme.fgDim,
1341
1296
  height: 1,
1342
1297
  wrapMode: "none"
1343
1298
  })
1344
1299
  );
1345
1300
  return box;
1346
1301
  }
1347
- function buildRow(renderer, idPrefix, cells) {
1302
+ function buildRow(renderer, idPrefix, cells, padX) {
1348
1303
  const row = new BoxRenderable(renderer, {
1349
1304
  id: idPrefix,
1350
1305
  flexDirection: "row",
@@ -1358,11 +1313,11 @@ function buildRow(renderer, idPrefix, cells) {
1358
1313
  height: 1,
1359
1314
  flexDirection: "row",
1360
1315
  flexShrink: 0,
1361
- paddingX: CELL_PAD_X
1316
+ paddingX: padX
1362
1317
  };
1363
1318
  if (cell.bg) cellOpts.backgroundColor = cell.bg;
1364
1319
  const cellBox = new BoxRenderable(renderer, cellOpts);
1365
- const innerWidth = Math.max(0, cell.width - CELL_PAD_X * 2);
1320
+ const innerWidth = Math.max(0, cell.width - padX * 2);
1366
1321
  const iconLen = cell.icon ? cell.icon.char.length + 1 : 0;
1367
1322
  const trailingLen = cell.trailing ? cell.trailing.char.length + 1 : 0;
1368
1323
  const textWidth = Math.max(0, innerWidth - iconLen - trailingLen);
@@ -1406,7 +1361,8 @@ function refreshHelp(ctx) {
1406
1361
  const {
1407
1362
  el: { helpBox },
1408
1363
  renderer,
1409
- state
1364
+ state,
1365
+ theme
1410
1366
  } = ctx;
1411
1367
  helpBox.visible = state.helpOpen;
1412
1368
  if (!state.helpOpen) return;
@@ -1426,7 +1382,7 @@ function refreshHelp(ctx) {
1426
1382
  });
1427
1383
  helpBox.add(scroll);
1428
1384
  lines.forEach((line, i) => {
1429
- scroll.content.add(buildHelpRow(renderer, `help-${i}`, line));
1385
+ scroll.content.add(buildHelpRow(renderer, `help-${i}`, line, theme));
1430
1386
  });
1431
1387
  return;
1432
1388
  }
@@ -1457,10 +1413,10 @@ function refreshHelp(ctx) {
1457
1413
  }
1458
1414
  }
1459
1415
  lines.slice(0, splitIdx).forEach(
1460
- (line, i) => left.add(buildHelpRow(renderer, `help-l-${i}`, line))
1416
+ (line, i) => left.add(buildHelpRow(renderer, `help-l-${i}`, line, theme))
1461
1417
  );
1462
1418
  lines.slice(splitIdx).forEach(
1463
- (line, i) => right.add(buildHelpRow(renderer, `help-r-${i}`, line))
1419
+ (line, i) => right.add(buildHelpRow(renderer, `help-r-${i}`, line, theme))
1464
1420
  );
1465
1421
  grid.add(left);
1466
1422
  grid.add(right);
@@ -1477,20 +1433,23 @@ function matrixTitle(matrix, state) {
1477
1433
  return ` Matrix · ${parts.join(" · ")} `;
1478
1434
  }
1479
1435
  function computeValueColWidth(ctx) {
1480
- const { renderer, matrix } = ctx;
1436
+ const { renderer, matrix, layout } = ctx;
1481
1437
  const available = Math.max(
1482
1438
  0,
1483
- renderer.terminalWidth - SIDEBAR_WIDTH - 6 - KEY_COL_WIDTH
1439
+ renderer.terminalWidth - layout.SIDEBAR_WIDTH - 6 - layout.KEY_COL_WIDTH
1484
1440
  );
1485
- const fair = matrix.files.length ? Math.floor(available / matrix.files.length) : VALUE_COL_MIN;
1486
- return Math.max(VALUE_COL_MIN, fair);
1441
+ const fair = matrix.files.length ? Math.floor(available / matrix.files.length) : layout.VALUE_COL_MIN;
1442
+ return Math.max(layout.VALUE_COL_MIN, fair);
1487
1443
  }
1488
1444
  function refreshMatrix(ctx) {
1489
1445
  const {
1490
1446
  el: { matrixBox, headerHost, scrollBox },
1491
1447
  renderer,
1492
1448
  matrix,
1493
- state
1449
+ state,
1450
+ theme,
1451
+ layout,
1452
+ heuristics
1494
1453
  } = ctx;
1495
1454
  const sectionOf = ctx.sectionOf;
1496
1455
  const valueColWidth = computeValueColWidth(ctx);
@@ -1498,16 +1457,21 @@ function refreshMatrix(ctx) {
1498
1457
  removeAllChildren(headerHost);
1499
1458
  removeAllChildren(scrollBox.content);
1500
1459
  headerHost.add(
1501
- buildRow(renderer, "header", [
1502
- { text: "KEY", fg: COLORS.fgHeader, width: KEY_COL_WIDTH },
1503
- ...matrix.files.map((f) => ({
1504
- text: basename(f.path),
1505
- fg: COLORS.fgHeader,
1506
- width: valueColWidth
1507
- }))
1508
- ])
1460
+ buildRow(
1461
+ renderer,
1462
+ "header",
1463
+ [
1464
+ { text: "KEY", fg: theme.fgHeader, width: layout.KEY_COL_WIDTH },
1465
+ ...matrix.files.map((f) => ({
1466
+ text: basename(f.path),
1467
+ fg: theme.fgHeader,
1468
+ width: valueColWidth
1469
+ }))
1470
+ ],
1471
+ layout.CELL_PAD_X
1472
+ )
1509
1473
  );
1510
- const totalWidth = KEY_COL_WIDTH + valueColWidth * matrix.files.length;
1474
+ const totalWidth = layout.KEY_COL_WIDTH + valueColWidth * matrix.files.length;
1511
1475
  const sectionStats = sectionMetadata(matrix, sectionOf, state);
1512
1476
  for (let r = 0; r < state.visibleItems.length; r++) {
1513
1477
  const item = state.visibleItems[r];
@@ -1521,18 +1485,25 @@ function refreshMatrix(ctx) {
1521
1485
  };
1522
1486
  const focused = state.mode === "browse" && r === state.rowIdx;
1523
1487
  scrollBox.content.add(
1524
- buildSectionDivider(renderer, `row-${r}`, sectionName, totalWidth, {
1525
- ...meta,
1526
- collapsed: state.collapsed.has(sectionKey),
1527
- focused
1528
- })
1488
+ buildSectionDivider(
1489
+ renderer,
1490
+ `row-${r}`,
1491
+ sectionName,
1492
+ totalWidth,
1493
+ {
1494
+ ...meta,
1495
+ collapsed: state.collapsed.has(sectionKey),
1496
+ focused
1497
+ },
1498
+ theme
1499
+ )
1529
1500
  );
1530
1501
  continue;
1531
1502
  }
1532
1503
  const key = item.ref;
1533
- const secret = isSecretKey(key) && !state.showSecrets;
1504
+ const secret = heuristics.isSecretKey(key) && !state.showSecrets;
1534
1505
  const cells = [
1535
- { text: key, fg: COLORS.fg, width: KEY_COL_WIDTH }
1506
+ { text: key, fg: theme.fg, width: layout.KEY_COL_WIDTH }
1536
1507
  ];
1537
1508
  for (let c = 0; c < matrix.files.length; c++) {
1538
1509
  const file = matrix.files[c];
@@ -1540,10 +1511,20 @@ function refreshMatrix(ctx) {
1540
1511
  const focused = state.mode === "browse" && r === state.rowIdx && c === state.colIdx;
1541
1512
  const isModified = state.modified.has(`${key}|${file.path}`);
1542
1513
  cells.push(
1543
- buildValueCell(cell, secret, valueColWidth, focused, isModified)
1514
+ buildValueCell(
1515
+ cell,
1516
+ secret,
1517
+ valueColWidth,
1518
+ focused,
1519
+ isModified,
1520
+ theme,
1521
+ heuristics.isPlaceholderValue
1522
+ )
1544
1523
  );
1545
1524
  }
1546
- scrollBox.content.add(buildRow(renderer, `row-${r}`, cells));
1525
+ scrollBox.content.add(
1526
+ buildRow(renderer, `row-${r}`, cells, layout.CELL_PAD_X)
1527
+ );
1547
1528
  }
1548
1529
  if (state.mode === "browse" && state.visibleItems.length > 0) {
1549
1530
  const target = `row-${state.rowIdx}`;
@@ -1572,7 +1553,9 @@ function refreshPrompt(ctx) {
1572
1553
  el: { promptBox, promptBody, promptHint },
1573
1554
  renderer,
1574
1555
  matrix,
1575
- state
1556
+ state,
1557
+ theme,
1558
+ heuristics
1576
1559
  } = ctx;
1577
1560
  const open = state.mode === "prompt" && state.prompt !== null;
1578
1561
  promptBox.visible = open;
@@ -1589,7 +1572,7 @@ function refreshPrompt(ctx) {
1589
1572
  new TextRenderable(renderer, {
1590
1573
  id: "prompt-input-text",
1591
1574
  content: `▸ ${state.promptInput}▏`,
1592
- fg: COLORS.fg,
1575
+ fg: theme.fg,
1593
1576
  height: 1,
1594
1577
  wrapMode: "none"
1595
1578
  })
@@ -1599,7 +1582,7 @@ function refreshPrompt(ctx) {
1599
1582
  new TextRenderable(renderer, {
1600
1583
  id: "prompt-error",
1601
1584
  content: `! ${state.message}`,
1602
- fg: COLORS.missing,
1585
+ fg: theme.missing,
1603
1586
  height: 1,
1604
1587
  marginTop: 1,
1605
1588
  wrapMode: "none"
@@ -1607,7 +1590,7 @@ function refreshPrompt(ctx) {
1607
1590
  );
1608
1591
  }
1609
1592
  if (p.kind === "edit" || p.kind === "add-value") {
1610
- const secret = isSecretKey(p.key) && !state.showSecrets;
1593
+ const secret = heuristics.isSecretKey(p.key) && !state.showSecrets;
1611
1594
  const nameWidth = Math.min(
1612
1595
  26,
1613
1596
  Math.max(...matrix.files.map((f) => basename(f.path).length + 2))
@@ -1616,7 +1599,7 @@ function refreshPrompt(ctx) {
1616
1599
  new TextRenderable(renderer, {
1617
1600
  id: "prompt-table-header",
1618
1601
  content: "Current values",
1619
- fg: COLORS.fgSection,
1602
+ fg: theme.fgSection,
1620
1603
  wrapMode: "none",
1621
1604
  height: 1,
1622
1605
  marginTop: 1
@@ -1636,7 +1619,7 @@ function refreshPrompt(ctx) {
1636
1619
  content: `${isTarget ? "▸" : " "} ${basename(file.path)}`.padEnd(
1637
1620
  nameWidth
1638
1621
  ),
1639
- fg: isTarget ? COLORS.fgBase : COLORS.fgDim,
1622
+ fg: isTarget ? theme.fgBase : theme.fgDim,
1640
1623
  height: 1,
1641
1624
  wrapMode: "none"
1642
1625
  })
@@ -1647,7 +1630,7 @@ function refreshPrompt(ctx) {
1647
1630
  new TextRenderable(renderer, {
1648
1631
  id: `prompt-row-${file.path}-value`,
1649
1632
  content: current,
1650
- fg: !entry ? COLORS.missing : isTarget ? COLORS.fg : COLORS.fgDim,
1633
+ fg: !entry ? theme.missing : isTarget ? theme.fg : theme.fgDim,
1651
1634
  height: 1,
1652
1635
  wrapMode: "none"
1653
1636
  })
@@ -1662,7 +1645,8 @@ function refreshSidebar(ctx) {
1662
1645
  renderer,
1663
1646
  matrix,
1664
1647
  allFiles,
1665
- state
1648
+ state,
1649
+ theme
1666
1650
  } = ctx;
1667
1651
  const total = allFiles.length;
1668
1652
  const enabled = state.enabled.size;
@@ -1676,13 +1660,13 @@ function refreshSidebar(ctx) {
1676
1660
  const matrixIdx = matrix.files.indexOf(file);
1677
1661
  const isFocusCol = isEnabled && matrixIdx === state.colIdx;
1678
1662
  const isPaneFocus = state.pane === "sidebar" && i === state.sidebarIdx;
1679
- const nameFg = !isEnabled ? COLORS.fgDim : isBase ? COLORS.fgBase : COLORS.fg;
1663
+ const nameFg = !isEnabled ? theme.fgDim : isBase ? theme.fgBase : theme.fg;
1680
1664
  const row = new BoxRenderable(renderer, {
1681
1665
  id: `file-${file.path}`,
1682
1666
  flexDirection: "row",
1683
1667
  height: 1,
1684
1668
  flexShrink: 0,
1685
- ...isPaneFocus ? { backgroundColor: COLORS.focusBg } : {}
1669
+ ...isPaneFocus ? { backgroundColor: theme.focusBg } : {}
1686
1670
  });
1687
1671
  const span = (id, text, fg) => new TextRenderable(renderer, {
1688
1672
  id: `${row.id}-${id}`,
@@ -1691,23 +1675,23 @@ function refreshSidebar(ctx) {
1691
1675
  height: 1,
1692
1676
  wrapMode: "none"
1693
1677
  });
1694
- row.add(span("focus", `${isPaneFocus ? "▶" : " "} `, COLORS.fg));
1678
+ row.add(span("focus", `${isPaneFocus ? "▶" : " "} `, theme.fg));
1695
1679
  row.add(
1696
1680
  span(
1697
1681
  "dirty",
1698
1682
  `${isDirty ? "●" : " "} `,
1699
- isDirty ? COLORS.fgDirty : COLORS.fgDim
1683
+ isDirty ? theme.fgDirty : theme.fgDim
1700
1684
  )
1701
1685
  );
1702
1686
  row.add(
1703
1687
  span(
1704
1688
  "base",
1705
1689
  `${isBase ? "★" : " "} `,
1706
- isBase ? COLORS.fgBase : COLORS.fgDim
1690
+ isBase ? theme.fgBase : theme.fgDim
1707
1691
  )
1708
1692
  );
1709
- row.add(span("col", `${isFocusCol ? "▸" : " "} `, COLORS.fgDim));
1710
- row.add(span("enabled", `${isEnabled ? "✓" : "☐"} `, COLORS.fgDim));
1693
+ row.add(span("col", `${isFocusCol ? "▸" : " "} `, theme.fgDim));
1694
+ row.add(span("enabled", `${isEnabled ? "✓" : "☐"} `, theme.fgDim));
1711
1695
  row.add(span("name", basename(file.path), nameFg));
1712
1696
  sidebar.add(row);
1713
1697
  }
@@ -1722,12 +1706,34 @@ function refreshAll(ctx) {
1722
1706
  refreshFilter(ctx);
1723
1707
  ctx.el.dimOverlay.visible = state.helpOpen || state.mode === "prompt" || state.mode === "filter";
1724
1708
  }
1725
- async function runMatrixTui(initialMatrix) {
1709
+ function resolveTheme(theme = {}, warn) {
1710
+ const hex = resolveThemeHex(theme, warn);
1711
+ const out = {};
1712
+ for (const key of Object.keys(DEFAULT_THEME_HEX)) {
1713
+ out[key] = RGBA.fromHex(hex[key]);
1714
+ }
1715
+ return out;
1716
+ }
1717
+ resolveTheme();
1718
+ function resolveLayout(layout) {
1719
+ return {
1720
+ KEY_COL_WIDTH: layout.keyColWidth,
1721
+ VALUE_COL_MIN: layout.valueColMin,
1722
+ SIDEBAR_WIDTH: layout.sidebarWidth,
1723
+ ROW_GAP: layout.rowGap,
1724
+ CELL_PAD_X: layout.cellPadX
1725
+ };
1726
+ }
1727
+ async function runMatrixTui(initialMatrix, config = DEFAULT_CONFIG) {
1726
1728
  const renderer = await createCliRenderer({ useMouse: true });
1729
+ const theme = resolveTheme(config.tui.theme);
1730
+ const layout = resolveLayout(config.tui.layout);
1731
+ const heuristics = resolveHeuristics(config);
1727
1732
  const allFiles = initialMatrix.files.slice();
1728
1733
  const hasBanners = initialMatrix.keys.some(
1729
1734
  (k) => initialMatrix.sectionOf(k) !== void 0
1730
1735
  );
1736
+ const grouping = heuristics.grouping === "auto" ? hasBanners ? "banner" : "prefix" : heuristics.grouping;
1731
1737
  const state = {
1732
1738
  mode: "browse",
1733
1739
  filter: "",
@@ -1740,24 +1746,28 @@ async function runMatrixTui(initialMatrix) {
1740
1746
  message: null,
1741
1747
  driftOnly: false,
1742
1748
  confirmQuit: false,
1743
- grouping: hasBanners ? "banner" : "prefix",
1749
+ grouping,
1744
1750
  helpOpen: false,
1745
1751
  undo: [],
1746
1752
  pane: "matrix",
1747
1753
  sidebarIdx: 0,
1748
1754
  enabled: new Set(allFiles),
1749
- showSecrets: false,
1755
+ showSecrets: !config.tui.maskSecrets,
1750
1756
  collapsed: /* @__PURE__ */ new Set(),
1751
1757
  modified: /* @__PURE__ */ new Set(),
1752
1758
  promptInput: ""
1753
1759
  };
1754
- const el = buildLayout(renderer);
1760
+ const el = buildLayout(renderer, theme, layout);
1755
1761
  let refreshScheduled = false;
1756
1762
  const ctx = {
1757
1763
  renderer,
1758
1764
  state,
1759
1765
  allFiles,
1760
1766
  el,
1767
+ config,
1768
+ theme,
1769
+ layout,
1770
+ heuristics,
1761
1771
  matrix: initialMatrix,
1762
1772
  currentBase: initialMatrix.base,
1763
1773
  refresh: () => {
@@ -1787,4 +1797,4 @@ async function runMatrixTui(initialMatrix) {
1787
1797
  export {
1788
1798
  runMatrixTui
1789
1799
  };
1790
- //# sourceMappingURL=app-CgJxxbhr.mjs.map
1800
+ //# sourceMappingURL=app-DgjyG_HE.mjs.map