thincoder 0.12.6 → 0.12.7
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/README.md +4 -0
- package/package.json +1 -1
- package/src/tui/render-conversation.mjs +15 -6
- package/src/tui/render.mjs +8 -3
package/README.md
CHANGED
|
@@ -209,6 +209,10 @@ Code conventions: pure `.mjs`, no semicolons, no npm dependencies allowed (inclu
|
|
|
209
209
|
|
|
210
210
|
## Changelog
|
|
211
211
|
|
|
212
|
+
### 0.12.7 (2026-08)
|
|
213
|
+
- **Fix: long replies and thinking are never folded** — the long-message folding feature (0.12.6) collapsed main output and reasoning behind a click when they exceeded 12 lines, hurting readability. Folding now applies to secondary dim-colored content (tool summaries/status) only; expanded blocks are exempt from the consecutive-dim folding so nothing folds twice.
|
|
214
|
+
- **Fix: wide tables misaligned in narrow terminals** — a many-column table that still exceeded the terminal width after column shrinking (e.g. 8 columns at 40 cols) wrapped at the terminal and misaligned. Table rows are now clipped with an ellipsis instead of ever exceeding the width.
|
|
215
|
+
|
|
212
216
|
### 0.12.6 (2026-08)
|
|
213
217
|
- **Checkpoint v2 — full-file-copy snapshots** — snapshots now store complete copies of changed files (tracked + untracked) instead of a git diff patch: rollback works even after commits happened post-snapshot. New `versions` checkpoint action lists a file's historical copies across snapshots (time / size / content hash) and restores a specific version. **Full rollback is disabled** (as dangerous as a working-tree reset — silently discards post-snapshot work); oversized files (>5MB) are skipped with an explicit notice; files created after a snapshot are never deleted by a restore.
|
|
214
218
|
- **Git destructive-command protection** — `checkout --` / `restore` / `reset --hard` / `clean -f` (including bypass variants like `checkout HEAD -- .`) auto-snapshot every uncommitted file **before** running, then execute without blocking — a model rollback can no longer destroy uncommitted work. Snapshot triggers slimmed to: destructive-git guard, pre-restore, manual.
|
package/package.json
CHANGED
|
@@ -52,13 +52,15 @@ function buildConvLines(state, cols) {
|
|
|
52
52
|
text = highlightSearchMatches(text, state.search.query, l._searchMatches, state.search.index, state.search.matches, i)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// Long-message folding: a single line
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
55
|
+
// Long-message folding: a single DIM line (tool summaries / status output — secondary
|
|
56
|
+
// content) that wraps beyond LONG_FOLD_LINES display rows collapses to
|
|
57
|
+
// [first, "… N more — click/expand", last]. MAIN OUTPUT (C.text replies) and
|
|
58
|
+
// THINKING (C.reason) are NEVER folded — folding them hurt readability (reported
|
|
59
|
+
// regression: long replies and thinking collapsed behind a click). Keyed by the
|
|
60
|
+
// source-line index (`long-${i}`) so the toggle survives re-renders.
|
|
59
61
|
const LONG_FOLD_LINES = 12
|
|
60
62
|
const longKey = `long-${i}`
|
|
61
|
-
const folded = state.foldEnabled !== false && !state.expandedBlocks?.has(longKey)
|
|
63
|
+
const folded = state.foldEnabled !== false && !state.expandedBlocks?.has(longKey) && l.color === C.dim
|
|
62
64
|
const block = []
|
|
63
65
|
for (const line of formatTables(sanitizeDisplay(text), cols - 1)) {
|
|
64
66
|
for (const wrapped of wrapText(line, cols - 1)) {
|
|
@@ -72,6 +74,11 @@ function buildConvLines(state, cols) {
|
|
|
72
74
|
convLines.push({ text: ` … ${block.length - 2} more lines — click to expand`, color: C.fold, _foldToggle: longKey, _src: i })
|
|
73
75
|
convLines.push(block[block.length - 1])
|
|
74
76
|
} else {
|
|
77
|
+
// Expanded long-DIM blocks must not re-trigger the consecutive-dim folding below
|
|
78
|
+
// (folding stacked on folding — reported regression). Only long blocks get the marker.
|
|
79
|
+
if (block.length > LONG_FOLD_LINES) {
|
|
80
|
+
for (const line of block) line._skipDimFold = true
|
|
81
|
+
}
|
|
75
82
|
convLines.push(...block)
|
|
76
83
|
}
|
|
77
84
|
}
|
|
@@ -113,7 +120,9 @@ function buildConvLines(state, cols) {
|
|
|
113
120
|
let j = i
|
|
114
121
|
while (j < convLines.length && convLines[j].color === C.dim) j++
|
|
115
122
|
const blockLen = j - i
|
|
116
|
-
|
|
123
|
+
// Expanded long-fold blocks are exempt — otherwise folding stacks on folding
|
|
124
|
+
const hasExpandedLong = convLines.slice(i, j).some((l) => l._skipDimFold)
|
|
125
|
+
if (blockLen > FOLD_LINES && !hasExpandedLong) {
|
|
117
126
|
const foldKey = `fold-${foldCounter++}`
|
|
118
127
|
if (state.foldEnabled !== false && !state.expandedBlocks?.has(foldKey)) {
|
|
119
128
|
folded.push(convLines[i])
|
package/src/tui/render.mjs
CHANGED
|
@@ -108,14 +108,19 @@ function renderTable(block, width) {
|
|
|
108
108
|
// Cell rendering: sliceByWidth truncates (single-line for header), padByWidth pads
|
|
109
109
|
const fmtCell = (text, ci) => padByWidth(sliceByWidth(text, widths[ci]), widths[ci])
|
|
110
110
|
const fmtRow = (cells) => "│ " + cells.map((c, i) => fmtCell(c, i)).join(" │ ") + " │"
|
|
111
|
+
// Many-column tables can still exceed `width` after shrinking to the 3-char floor
|
|
112
|
+
// (e.g. 8 columns → 8×3 + 25 borders = 49 > 40). Rows wider than the terminal would
|
|
113
|
+
// wrap and misalign — clip the row instead, with an ellipsis (fixes the reported
|
|
114
|
+
// "table no longer aligns" regression in narrow windows).
|
|
115
|
+
const clip = (line) => stringWidth(line) > width ? sliceByWidth(line, Math.max(1, width - 1)) + "…" : line
|
|
111
116
|
|
|
112
117
|
// separator line
|
|
113
118
|
const separator = "├" + widths.map((w) => "─".repeat(w + 2)).join("┼") + "┤"
|
|
114
119
|
|
|
115
120
|
const out = []
|
|
116
121
|
// Header: single-line truncation (header labels are usually short, truncation beats wrapping)
|
|
117
|
-
out.push(fmtRow(rows[0]))
|
|
118
|
-
out.push(separator)
|
|
122
|
+
out.push(clip(fmtRow(rows[0])))
|
|
123
|
+
out.push(clip(separator))
|
|
119
124
|
|
|
120
125
|
// Data rows: over-long cells wrap by column width; one logical row may produce multiple display lines
|
|
121
126
|
for (let r = 2; r < rows.length; r++) {
|
|
@@ -123,7 +128,7 @@ function renderTable(block, width) {
|
|
|
123
128
|
const wrapped = rows[r].map((cell, ci) => wrapText(cell, widths[ci]))
|
|
124
129
|
const height = Math.max(...wrapped.map((lines) => lines.length))
|
|
125
130
|
for (let lineIdx = 0; lineIdx < height; lineIdx++) {
|
|
126
|
-
out.push(fmtRow(wrapped.map((lines) => lines[lineIdx] ?? "")))
|
|
131
|
+
out.push(clip(fmtRow(wrapped.map((lines) => lines[lineIdx] ?? ""))))
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|