@askalf/dario 5.4.3 → 5.4.4

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.
@@ -143,7 +143,15 @@ export function truncate(text, maxWidth, ellipsis = '…') {
143
143
  visible++;
144
144
  i++;
145
145
  }
146
- return out + ellipsis;
146
+ // Flush SGR sequences from the clipped remainder. Without this, a cut
147
+ // that lands inside a dim()/fg() block drops the closing code, leaving
148
+ // the attribute open — it then bleeds into the following line, and past
149
+ // the frame entirely (clearScreen does not reset SGR). Replaying the
150
+ // remainder's own codes rather than appending a blanket reset keeps an
151
+ // enclosing inverse() wrapper intact; clipped openers arrive paired
152
+ // with their closers, so they render as a no-op.
153
+ const tail = text.slice(i).match(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g);
154
+ return out + ellipsis + (tail ? tail.join('') : '');
147
155
  }
148
156
  /** Pad `text` (right-aligned by default 'left' fills right side) to `width`. */
149
157
  export function pad(text, width, align = 'left') {
@@ -19,7 +19,7 @@
19
19
  * change. The DarioConfig schema is the source of truth — adding a
20
20
  * field there + a row here lights it up.
21
21
  */
22
- import { fg, dim, brand, inverse, pad } from '../render.js';
22
+ import { fg, dim, brand, inverse, pad, truncate } from '../render.js';
23
23
  import { CONFIG_SCHEMA_VERSION, defaultConfig, loadConfig, saveConfig, } from '../../config-file.js';
24
24
  /**
25
25
  * The visible field registry. Order = display order. New fields just
@@ -101,40 +101,55 @@ export const ConfigTab = {
101
101
  const lines = [];
102
102
  const w = dimv.cols;
103
103
  const labelW = 26;
104
- const valueW = w - labelW - 6;
104
+ // Fixed value column so the hint keeps room on the same row. Every
105
+ // row is padded/truncated to exactly `w` below: the frame is drawn
106
+ // from the top with no scrollback, so a row that wraps costs a
107
+ // second physical line and pushes the panel head off-screen.
108
+ const valueW = Math.max(6, Math.min(16, w - labelW - 4));
109
+ // Rows that fit between the title (+blank) and the trailing blank
110
+ // + prompt/status pair. Window FIELDS around the selection so the
111
+ // list scrolls instead of overflowing the body region.
112
+ const listRows = Math.max(1, dimv.rows - 5);
113
+ const startIdx = Math.max(0, Math.min(state.selectedIdx - Math.floor(listRows / 3), FIELDS.length - listRows));
114
+ const endIdx = Math.min(startIdx + listRows, FIELDS.length);
105
115
  const dirty = isDirty(state);
106
116
  const title = dirty
107
117
  ? brand('Config') + dim(' — ') + fg('yellow', '● unsaved changes')
108
118
  : brand('Config');
109
- lines.push(' ' + title);
119
+ const counter = FIELDS.length > listRows
120
+ ? dim(` ${state.selectedIdx + 1} / ${FIELDS.length}`)
121
+ : '';
122
+ lines.push(truncate(' ' + title + counter, w));
110
123
  lines.push('');
111
- for (let i = 0; i < FIELDS.length; i++) {
124
+ for (let i = startIdx; i < endIdx; i++) {
112
125
  const field = FIELDS[i];
113
126
  const value = getByPath(state.config, field.path);
114
127
  const orig = getByPath(state.snapshot, field.path);
115
128
  const changed = !Object.is(value, orig);
116
129
  const valueRender = renderValue(field, value, changed);
117
130
  const hint = field.hint ? ' ' + dim('— ' + field.hint) : '';
118
- const row = ' ' + pad(field.label + ':', labelW) + pad(valueRender, valueW) + hint;
131
+ const row = pad(truncate(' ' + pad(field.label + ':', labelW) + pad(valueRender, valueW) + hint, w), w);
119
132
  lines.push(i === state.selectedIdx ? inverse(row) : row);
120
133
  }
121
134
  // ── Edit prompt or status line ─────────────────────────────
122
135
  lines.push('');
123
136
  if (state.editBuffer !== null) {
124
137
  const f = FIELDS[state.selectedIdx];
125
- lines.push(' ' + fg('cyan', `Edit ${f.label}:`) + ' ' + state.editBuffer + fg('cyan', '_'));
126
- lines.push(' ' + dim('Enter to confirm · Esc to cancel'));
138
+ lines.push(truncate(' ' + fg('cyan', `Edit ${f.label}:`) + ' ' + state.editBuffer + fg('cyan', '_'), w));
139
+ lines.push(truncate(' ' + dim('Enter to confirm · Esc to cancel'), w));
127
140
  }
128
141
  else if (state.statusMessage) {
129
142
  const color = state.statusKind === 'error' ? 'red'
130
143
  : state.statusKind === 'success' ? 'green'
131
144
  : 'cyan';
132
- lines.push(' ' + fg(color, state.statusMessage));
145
+ lines.push(truncate(' ' + fg(color, state.statusMessage), w));
133
146
  }
134
147
  else {
135
- lines.push(' ' + dim('↑↓ navigate · Enter edit · s save · d discard · r reload'));
148
+ lines.push(truncate(' ' + dim('↑↓ navigate · Enter edit · s save · d discard · r reload'), w));
136
149
  }
137
- return lines.join('\n');
150
+ // Hard floor for terminals too short for even one field + prompt:
151
+ // drop trailing rows rather than let the panel run past the body.
152
+ return lines.slice(0, Math.max(1, dimv.rows)).join('\n');
138
153
  },
139
154
  };
140
155
  // ── Helpers ───────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "5.4.3",
3
+ "version": "5.4.4",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {