zames_pro 2.96.5 → 2.96.6

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.96.6] - 2026-11-06
11
+
12
+ ### Fixed
13
+
14
+ - **input:** drop a dangling ZWJ when a truncation cuts an emoji sequence
15
+ - **input:** count a ZWJ emoji sequence as one glyph in the layout
16
+ - **tui:** ZAMES_NO_BG=0 no longer disables the themed background
17
+ - **markdown:** an unknown code-fence language falls back to plain text
18
+ - **web:** decode an escaped HTML entity only once
19
+
10
20
  ## [2.96.5] - 2026-11-06
11
21
 
12
22
  ### Fixed
@@ -167,6 +167,15 @@ export function visRows(s, cols) {
167
167
  // zero-width joiner/space/variation selectors count as 0.
168
168
  export function visLen(s) {
169
169
  s = String(s);
170
+ // An emoji SEQUENCE renders as ONE glyph but occupies several code
171
+ // points: ZWJ-joined, a skin-tone modifier, a keycap. Counting each
172
+ // point separately measured a family as 6 columns instead of 2, so the
173
+ // layout reserved extra rows and the cursor column drifted. The sequence
174
+ // is replaced by ONE placeholder cell before measuring.
175
+ s = s.replace(/(?:\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u200d|\ufe0f)+/gu,
176
+ // The placeholder is a WIDE glyph (2 columns), the width an emoji renders
177
+ // at: a single `X` would under-count it.
178
+ '😀');
170
179
  let n = 0;
171
180
  let i = 0;
172
181
  while (i < s.length) {
@@ -399,6 +408,11 @@ export function truncateToWidth(s, maxCols) {
399
408
  used += w;
400
409
  i += ch.length;
401
410
  }
411
+ // A cut inside a ZWJ emoji sequence leaves a DANGLING joiner: the terminal
412
+ // glues the following `…` to the emoji, so the marker becomes invisible and
413
+ // the visible line is wider than requested. Drop the dangling tail (the
414
+ // whole incomplete grapheme) before closing the style.
415
+ out = out.replace(/\u200d+$/, '');
402
416
  // The visible text was cut BEFORE any trailing RESET, so the ellipsis and
403
417
  // everything printed after it on the row would inherit the last colour.
404
418
  // Close any style the kept prefix opened (the text may still be plain, in
package/dist/markdown.js CHANGED
@@ -98,11 +98,31 @@ export function renderMarkdown(text) {
98
98
  // opt-out is NO_COLOR, handled above.
99
99
  color: true,
100
100
  theme: mdTheme(p),
101
- highlighter: (code, lang) => highlight(code, {
102
- language: lang || 'plaintext',
103
- ignoreIllegals: true,
104
- theme: hlTheme(p),
105
- }),
101
+ // An UNKNOWN language tag throws inside cli-highlight, and the throw
102
+ // escaped the whole render: ONE ```` ```kotlin-ish ```` fence turned the
103
+ // entire answer into raw markdown (no wrap, no theme, no code box). Fall
104
+ // back to plaintext for this block only.
105
+ highlighter: (code, lang) => {
106
+ try {
107
+ return highlight(code, {
108
+ language: lang || 'plaintext',
109
+ ignoreIllegals: true,
110
+ theme: hlTheme(p),
111
+ });
112
+ }
113
+ catch {
114
+ try {
115
+ return highlight(code, {
116
+ language: 'plaintext',
117
+ ignoreIllegals: true,
118
+ theme: hlTheme(p),
119
+ });
120
+ }
121
+ catch {
122
+ return String(code);
123
+ }
124
+ }
125
+ },
106
126
  codeBox: true,
107
127
  tableBorder: 'unicode',
108
128
  listIndent: 2,
@@ -23,7 +23,13 @@ function hexToOsc(hex) {
23
23
  * testable without a terminal.
24
24
  */
25
25
  export function backgroundOsc(name) {
26
- if (process.env.NO_COLOR || process.env.ZAMES_NO_BG)
26
+ // NO_COLOR is "present means off" (no-color.org). ZAMES_NO_BG is our OWN
27
+ // switch: a shell profile writing `ZAMES_NO_BG=0` clearly means KEEP it, so
28
+ // only a truthy value opts out.
29
+ const noBg = String(process.env.ZAMES_NO_BG ?? '').toLowerCase();
30
+ if (process.env.NO_COLOR)
31
+ return '';
32
+ if (noBg && noBg !== '0' && noBg !== 'false' && noBg !== 'no')
27
33
  return '';
28
34
  const id = isThemeName(name) ? name : DEFAULT_THEME;
29
35
  return '\x1b]11;rgb:' + hexToOsc(ALL_PALETTES[id].bg) + '\x07';
package/dist/web.js CHANGED
@@ -77,10 +77,13 @@ export function htmlToText(html) {
77
77
  // A parked `<pre>` block comes back AFTER the tag strip, with only its own
78
78
  // tags removed and its text kept verbatim.
79
79
  s = s.replace(/\u0000PRE(\d+)PRE\u0000/g, (_m, i) => (parkedPre[Number(i)] ?? '').replace(/<[^>]+>/g, ''));
80
- // HTML entities — the basic ones
80
+ // HTML entities — the basic ones. `&amp;` is decoded LAST, otherwise an
81
+ // escaped entity (`&amp;#38;` — the literal text `&#38;`) is decoded TWICE
82
+ // and the page's text is changed.
83
+ const AMP = String.fromCharCode(0) + 'amp' + String.fromCharCode(0);
84
+ s = s.replace(/&amp;/g, AMP);
81
85
  s = s
82
86
  .replace(/&nbsp;/g, ' ')
83
- .replace(/&amp;/g, '&')
84
87
  .replace(/&lt;/g, '<')
85
88
  .replace(/&gt;/g, '>')
86
89
  .replace(/&quot;/g, '"')
@@ -89,7 +92,9 @@ export function htmlToText(html) {
89
92
  .replace(/&ndash;/g, '–')
90
93
  .replace(/&hellip;/g, '…')
91
94
  .replace(/&#(\d+);/g, (_, n) => decodeEntityCode(Number(n)))
92
- .replace(/&#x([0-9a-f]+);/gi, (_, n) => decodeEntityCode(parseInt(n, 16)));
95
+ .replace(/&#x([0-9a-f]+);/gi, (_, n) => decodeEntityCode(parseInt(n, 16)))
96
+ .split(AMP)
97
+ .join('&');
93
98
  // Collapse blank lines
94
99
  s = s
95
100
  .split('\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.96.5",
3
+ "version": "2.96.6",
4
4
  "description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen or GLM — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
5
5
  "type": "module",
6
6
  "bin": {