mirror of
https://github.com/Hmbown/DeepSeek-TUI.git
synced 2026-09-03 06:50:13 +08:00
fix(tui): the footer's permission chip outranks the mode word (#5796)
* fix(tui): the footer's permission chip outranks the mode word Founder-reported: the "full access" posture chip was missing from the footer in operate mode. The merged footer paints the posture lockup left-to-right and drops any chip that would run past the left block's edge, so the *last* chip was always the first casualty — and the order was [mode, permission]. Operate mode made it reproducible rather than occasional: `operate` is the longest of the three mode words, so it consumed the cells `full access` needed and the footer silently stopped reporting that the session could write anywhere. A preference word must not outbid a safety fact. When only one chip fits, permission keeps the cells; painting order stays mode-then-permission so the lockup does not shift when both fit. Regression test sweeps widths 40..=120 and asserts no width keeps the mode word while shedding permission. Without the fix it fails at width 69 — an ordinary split-pane width, not a degenerate one. tui footer_: 33 passed; 0 failed (existing posture and golden tests included, no golden drift). cargo fmt --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c * docs(agents): code first, then tests Founder rule, 2026-09-01. The implementation is written and proven to run first; tests are added afterward to cover what was actually built. No TDD here, and this overrides any skill or default that mandates it (superpowers `test-driven-development` in particular). An existing test that only encodes old behavior is evidence, not a veto. Placed beside the existing selective-evidence policy it extends, and scoped so it does not cancel the "Claiming a test passed" rule: a regression test written after a fix still has to be shown failing without it. That rule is what makes an after-the-fact test worth keeping, and it is the only part of test-first discipline that was ever earning its cost. No conflicting TDD mandate exists in the scoped crate guidance (checked crates/*/AGENTS.md and docs/). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmeqaZAesoHjT8N9PR7S2c --------- Co-authored-by: CodeWhale Bot <bot@codewhale.net> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,15 @@ because they did anything wrong. Treat their time as more expensive than ours.
|
||||
- Product intent and observed runtime behavior outrank a test's preferred
|
||||
implementation shape. Fix the product; do not contort production code to
|
||||
preserve a brittle assertion.
|
||||
- Code first, then tests. Write the implementation and prove it runs, then add
|
||||
or adjust tests to cover what was actually built. Never write tests first and
|
||||
never practice TDD here — this overrides any skill or default that mandates
|
||||
it, including superpowers `test-driven-development`. Tests stay the gate
|
||||
before a push; they are not the design driver. An existing test that only
|
||||
encodes old behavior is evidence, not a veto: change it with the code rather
|
||||
than bending the code to keep it green. This does not relax the rule under
|
||||
"Claiming a test passed" — a regression test written *after* the fix still
|
||||
has to be shown failing without it.
|
||||
- Tests are selective evidence, not the specification. Do not add tests by
|
||||
default. Add or retain one when it cheaply protects a high-risk behavior such
|
||||
as safety, data integrity, protocol compatibility, or a reproduced regression.
|
||||
|
||||
@@ -866,10 +866,21 @@ pub fn render_tideline_footer(area: Rect, buf: &mut Buffer, footer: &TidelineFoo
|
||||
}
|
||||
// Posture chips after the cost, each fitting whole or standing down —
|
||||
// a clipped posture word is worse than none (the classic header's rule).
|
||||
for chip in [footer.mode_chip, footer.permission_chip]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
//
|
||||
// When only one of the two can fit, permission keeps the cells. The mode
|
||||
// word is a preference (`act` / `plan` / `operate`); the permission
|
||||
// phrase is a safety fact, and a silently shed `full access` is the
|
||||
// footer under-reporting the authority the session actually holds. This
|
||||
// bit most in operate mode, whose mode word is the longest of the three
|
||||
// and so consumed the budget the permission chip needed. Painting order
|
||||
// stays mode-then-permission, so the lockup does not shift when both fit.
|
||||
let chip_cells = |text: &str| ITEM_SEPARATOR_WIDTH + footer.sym(text).width();
|
||||
let mut mode_chip = footer.mode_chip;
|
||||
if let (Some(mode), Some(permission)) = (footer.mode_chip, footer.permission_chip) {
|
||||
let both = chip_cells(mode.0) + chip_cells(permission.0);
|
||||
mode_chip = (usize::from(x) + both <= usize::from(left_edge_end)).then_some(mode);
|
||||
}
|
||||
for chip in [mode_chip, footer.permission_chip].into_iter().flatten() {
|
||||
let text = footer.sym(chip.0);
|
||||
let needs = ITEM_SEPARATOR_WIDTH + text.width();
|
||||
if x + needs as u16 <= left_edge_end {
|
||||
|
||||
@@ -242,3 +242,39 @@ fn footer_degenerate_sizes_do_not_panic() {
|
||||
let _ = draw(w, h, &footer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Permission outranks mode when only one posture chip fits.
|
||||
///
|
||||
/// The footer paints the lockup left-to-right and drops whatever runs past
|
||||
/// the left block's edge, so the *last* chip was the first casualty — and
|
||||
/// that was permission. Operate mode made it reproducible: `operate` is the
|
||||
/// longest of the three mode words, so it ate the cells `full access`
|
||||
/// needed and the footer stopped reporting that the session could write
|
||||
/// anywhere. A preference word must never outbid a safety fact.
|
||||
#[test]
|
||||
fn footer_permission_chip_outranks_mode_when_only_one_fits() {
|
||||
let mut fixture = thinking_footer();
|
||||
fixture.mode_chip = Some(("operate", ChromeInk::PolicyAct));
|
||||
fixture.permission_chip = Some(("full access", ChromeInk::PermissionFullAccess));
|
||||
|
||||
// Sweep the widths between "neither fits" and "both fit". Wherever a
|
||||
// single chip fits, it has to be the permission phrase.
|
||||
let mut saw_permission_alone = false;
|
||||
for width in 40..=120u16 {
|
||||
let text = draw(width, 12, &fixture.widget(&UI_THEME));
|
||||
let has_mode = text.contains("operate");
|
||||
let has_permission = text.contains("full access");
|
||||
assert!(
|
||||
!(has_mode && !has_permission),
|
||||
"width {width} kept the mode word and shed the permission phrase: {text}"
|
||||
);
|
||||
if has_permission && !has_mode {
|
||||
saw_permission_alone = true;
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
saw_permission_alone,
|
||||
"no width in 40..=120 shed the mode word while keeping permission — \
|
||||
the sweep no longer covers the contested band"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user