Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project uses [independent versioning](README.md#versioning) for Framewo

---

## CLI 3.2.5 — Smarter Table Column Allocation in `explore`

### Fixed (CLI)
- Table column widths in `devtrail explore` are now allocated with a water-fill strategy: narrow columns (e.g. `CWE`, `Severity`) receive exactly their natural width and the excess flows to the columns that need it (e.g. `Description`, `Remediation`). Previously, a proportional pass gave every column a slice of the terminal budget regardless of need, which caused the narrow columns to hoard space and the wide ones to wrap unnecessarily. This is what produced the "fixes itself, breaks, fixes itself again" behavior users saw while resizing the terminal.

---

## CLI 3.2.4 — Unicode-Safe Rendering Across TUI and Commands

### Fixed (CLI)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ DevTrail uses independent version tags for each component:
| Component | Tag prefix | Example | Includes |
|-----------|-----------|---------|----------|
| Framework | `fw-` | `fw-4.2.0` | Templates (12 types), governance, directives |
| CLI | `cli-` | `cli-3.2.4` | The `devtrail` binary |
| CLI | `cli-` | `cli-3.2.5` | The `devtrail` binary |

Check installed versions with `devtrail status` or `devtrail about`.

Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "devtrail-cli"
version = "3.2.4"
version = "3.2.5"
edition = "2021"
description = "CLI tool for DevTrail - Documentation Governance for AI-Assisted Development"
license = "MIT"
Expand Down
130 changes: 104 additions & 26 deletions cli/src/tui/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,9 @@ fn compute_column_widths(
}
}
}
const MIN_COL: usize = 3;
for w in &mut natural {
*w = (*w).max(3);
*w = (*w).max(MIN_COL);
}

// Overhead: indent is handled outside; here we account for borders
Expand All @@ -368,32 +369,36 @@ fn compute_column_widths(
return natural;
}

// Distribute available width proportionally
// Water-fill allocation (classic: assign in ascending order of demand).
// For each column in order of natural width ascending, give it the
// smaller of its natural width and a fair share of the remaining
// budget. Narrow columns settle early with exactly what they need; the
// leftover rolls over to the wider columns that still have deficit.
// The old proportional pass gave every column a slice of the budget
// regardless of need, so narrow columns hoarded space while wide ones
// wrapped unnecessarily — exactly the "growing then shrinking" behavior
// users saw when resizing the terminal.
let mut order: Vec<usize> = (0..num_cols).collect();
order.sort_by_key(|&i| natural[i]);

let mut widths = vec![0usize; num_cols];
for (i, &nat) in natural.iter().enumerate() {
widths[i] = ((nat as f64 / total_natural as f64) * content_budget as f64).floor() as usize;
widths[i] = widths[i].max(3);
}

// Distribute any remaining space to the largest columns
let assigned: usize = widths.iter().sum();
let mut remaining = content_budget.saturating_sub(assigned);
while remaining > 0 {
// Find column with largest deficit
let mut best = 0;
let mut best_deficit = 0usize;
for (i, (&nat, &w)) in natural.iter().zip(widths.iter()).enumerate() {
let deficit = nat.saturating_sub(w);
if deficit > best_deficit {
best_deficit = deficit;
best = i;
}
}
if best_deficit == 0 {
break;
}
widths[best] += 1;
remaining -= 1;
let mut remaining_budget = content_budget;
let mut cols_left = num_cols;

for &i in &order {
let fair_share = if cols_left > 0 { remaining_budget / cols_left } else { 0 };
let alloc = if natural[i] <= fair_share {
// Column fits in its fair share — give it the full natural width.
natural[i]
} else {
// Column wants more than fair share — give it the fair share,
// but never less than MIN_COL (so it stays visible) and never
// more than its natural width.
fair_share.max(MIN_COL).min(natural[i])
};
widths[i] = alloc;
remaining_budget = remaining_budget.saturating_sub(alloc);
cols_left -= 1;
}

widths
Expand Down Expand Up @@ -716,4 +721,77 @@ mod tests {
let content: usize = widths.iter().sum();
assert!(content + border_overhead <= available);
}

/// Regression test for the "wide column starves" bug. With a mix of one
/// very wide column and several narrow ones, the old proportional
/// allocator gave every column a share of the budget regardless of
/// need, so narrow columns ended up with more space than they could
/// use while the wide one still wrapped. Water-fill should give each
/// narrow column exactly its natural width and pour the rest into the
/// wide column.
#[test]
fn water_fill_narrow_columns_do_not_hoard() {
let header: Vec<String> = vec![
"Vuln ID".to_string(),
"CWE".to_string(),
"Severity".to_string(),
"Description".to_string(),
];
let body: Vec<Vec<String>> = vec![
vec![
"VULN-001".to_string(),
"CWE-863".to_string(),
"7.1".to_string(),
// Very long description that demands most of the budget.
"RevokeAPIKey does not validate that key_id belongs to the service_id parameter. SQL query UpdateAPIKeyStatus filters only by key_id.".to_string(),
],
];
let available = 120;
let widths = compute_column_widths(&header, &body, available);
assert_eq!(widths.len(), 4);

// Natural widths: Vuln=max("Vuln ID"=7, "VULN-001"=8)=8, CWE=7,
// Severity=8, Description≈137. Narrow columns must receive exactly
// their natural width; the rest flows to Description.
assert_eq!(widths[0], 8, "Vuln ID column got {} cols, expected 8", widths[0]);
assert_eq!(widths[1], 7, "CWE column got {} cols, expected 7", widths[1]);
assert_eq!(widths[2], 8, "Severity column got {} cols, expected 8", widths[2]);

let border_overhead = 2 + (4 - 1) * 3 + 2; // 13
let expected_desc = available - border_overhead - (8 + 7 + 8);
assert_eq!(
widths[3], expected_desc,
"Description got {} cols, expected {} (all leftover)",
widths[3], expected_desc,
);
}

#[test]
fn water_fill_tight_budget_does_not_overflow() {
// Budget smaller than sum of naturals; no column should exceed its
// natural width and the total must fit in the content budget.
let header: Vec<String> = vec!["A".into(), "B".into(), "C".into(), "D".into()];
let body: Vec<Vec<String>> = vec![vec![
"short".into(),
"mediumtext".into(),
"wide column content here".into(),
"xxx".into(),
]];
let available = 40;
let widths = compute_column_widths(&header, &body, available);
let border_overhead = 2 + (widths.len() - 1) * 3 + 2;
let total: usize = widths.iter().sum();
assert!(
total + border_overhead <= available,
"total {} + overhead {} exceeds budget {}",
total,
border_overhead,
available,
);
// No column exceeds its natural width.
let naturals = [5usize, 10, 24, 3];
for (i, w) in widths.iter().enumerate() {
assert!(*w <= naturals[i].max(3), "col {i} exceeded its natural");
}
}
}
12 changes: 6 additions & 6 deletions docs/adopters/CLI-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ DevTrail uses **independent version tags** for each component:
| Component | Tag prefix | Example | What it includes |
|-----------|-----------|---------|------------------|
| Framework | `fw-` | `fw-4.2.0` | Templates (12 types), governance docs, directives |
| CLI | `cli-` | `cli-3.2.4` | The `devtrail` binary |
| CLI | `cli-` | `cli-3.2.5` | The `devtrail` binary |

Framework and CLI are released independently. A framework update does not require a CLI update, and vice versa.

Expand Down Expand Up @@ -110,7 +110,7 @@ $ devtrail update
Updating framework...
✔ Framework updated to fw-4.2.0
Updating CLI...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -143,11 +143,11 @@ Use `--method` to override auto-detection: `--method=github` or `--method=cargo`

```bash
$ devtrail update-cli
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5

$ devtrail update-cli --method=cargo
Compiling from source, this may take a few minutes...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -210,7 +210,7 @@ $ devtrail status
┌───────────┬──────────────────────────┐
│ Path │ /home/user/my-project │
│ Framework │ fw-4.2.0 │
│ CLI │ cli-3.2.4
│ CLI │ cli-3.2.5
│ Language │ en │
└───────────┴──────────────────────────┘

Expand Down Expand Up @@ -634,7 +634,7 @@ Show version, authorship, and license information.
```bash
$ devtrail about
DevTrail CLI
CLI version: cli-3.2.4
CLI version: cli-3.2.5
Framework version: fw-4.2.0
Author: Strange Days Tech, S.A.S.
License: MIT
Expand Down
2 changes: 1 addition & 1 deletion docs/i18n/es/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ DevTrail usa tags de versión independientes para cada componente:
| Componente | Prefijo de tag | Ejemplo | Incluye |
|------------|---------------|---------|---------|
| Framework | `fw-` | `fw-4.2.0` | Plantillas (12 tipos), gobernanza, directivas |
| CLI | `cli-` | `cli-3.2.4` | El binario `devtrail` |
| CLI | `cli-` | `cli-3.2.5` | El binario `devtrail` |

Verifica las versiones instaladas con `devtrail status` o `devtrail about`.

Expand Down
12 changes: 6 additions & 6 deletions docs/i18n/es/adopters/CLI-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ DevTrail usa **tags de versión independientes** para cada componente:
| Componente | Prefijo de tag | Ejemplo | Qué incluye |
|------------|---------------|---------|-------------|
| Framework | `fw-` | `fw-4.2.0` | Plantillas (12 tipos), docs de gobernanza, directivas |
| CLI | `cli-` | `cli-3.2.4` | El binario `devtrail` |
| CLI | `cli-` | `cli-3.2.5` | El binario `devtrail` |

Framework y CLI se publican de forma independiente. Una actualización del framework no requiere actualización del CLI, y viceversa.

Expand Down Expand Up @@ -109,7 +109,7 @@ $ devtrail update
Updating framework...
✔ Framework updated to fw-4.2.0
Updating CLI...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -142,11 +142,11 @@ Usa `--method` para forzar el método: `--method=github` o `--method=cargo`.

```bash
$ devtrail update-cli
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5

$ devtrail update-cli --method=cargo
Compiling from source, this may take a few minutes...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -204,7 +204,7 @@ DevTrail Status
───────────────
Path: /home/user/my-project
Framework version: fw-4.2.0
CLI version: cli-3.2.4
CLI version: cli-3.2.5
Language: en
Structure: ✔ Complete

Expand Down Expand Up @@ -513,7 +513,7 @@ Muestra información de versión, autoría y licencia.
```bash
$ devtrail about
DevTrail CLI
CLI version: cli-3.2.4
CLI version: cli-3.2.5
Framework version: fw-4.2.0
Author: Strange Days Tech, S.A.S.
License: MIT
Expand Down
2 changes: 1 addition & 1 deletion docs/i18n/zh-CN/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ DevTrail 为每个组件使用独立的版本标签:
| 组件 | 标签前缀 | 示例 | 包含内容 |
|------|----------|------|----------|
| Framework | `fw-` | `fw-4.2.0` | 模板(12 种类型)、治理文档、指令 |
| CLI | `cli-` | `cli-3.2.4` | `devtrail` 二进制文件 |
| CLI | `cli-` | `cli-3.2.5` | `devtrail` 二进制文件 |

使用 `devtrail status` 或 `devtrail about` 查看已安装的版本。

Expand Down
12 changes: 6 additions & 6 deletions docs/i18n/zh-CN/adopters/CLI-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ DevTrail 为每个组件使用**独立的版本标签**:
| 组件 | 标签前缀 | 示例 | 包含内容 |
|------|----------|------|----------|
| Framework | `fw-` | `fw-4.2.0` | 模板(12 种类型)、治理文档、指令 |
| CLI | `cli-` | `cli-3.2.4` | `devtrail` 二进制文件 |
| CLI | `cli-` | `cli-3.2.5` | `devtrail` 二进制文件 |

Framework 和 CLI 独立发布。Framework 更新不需要 CLI 更新,反之亦然。

Expand Down Expand Up @@ -110,7 +110,7 @@ $ devtrail update
Updating framework...
✔ Framework updated to fw-4.2.0
Updating CLI...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -143,11 +143,11 @@ $ devtrail update-framework

```bash
$ devtrail update-cli
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5

$ devtrail update-cli --method=cargo
Compiling from source, this may take a few minutes...
✔ CLI updated to cli-3.2.4
✔ CLI updated to cli-3.2.5
```

---
Expand Down Expand Up @@ -210,7 +210,7 @@ $ devtrail status
┌───────────┬──────────────────────────┐
│ Path │ /home/user/my-project │
│ Framework │ fw-4.2.0 │
│ CLI │ cli-3.2.4
│ CLI │ cli-3.2.5
│ Language │ en │
└───────────┴──────────────────────────┘

Expand Down Expand Up @@ -634,7 +634,7 @@ $ devtrail explore
```bash
$ devtrail about
DevTrail CLI
CLI version: cli-3.2.4
CLI version: cli-3.2.5
Framework version: fw-4.2.0
Author: Strange Days Tech, S.A.S.
License: MIT
Expand Down