Skip to content

feat(tui): 一覧の段組み(左右分割)表示#9

Merged
yuu61 merged 6 commits into
masterfrom
feature/split-view
Jun 10, 2026
Merged

feat(tui): 一覧の段組み(左右分割)表示#9
yuu61 merged 6 commits into
masterfrom
feature/split-view

Conversation

@yuu61

@yuu61 yuu61 commented Jun 10, 2026

Copy link
Copy Markdown
Owner

概要

端末の縦幅が足りないが横幅に余裕がある場合に、単一の監視リストを新聞段組み(列メジャー: 縦→横)で複数列に折り返して表示する機能を追加します。tmux で deadman を複数立ち上げて画面を分割する運用の代替で、deadman 単一プロセスでタイトル・キー凡例を 1 つだけ共有するため、節約したい縦行をヘッダ重複で浪費しません。

「同じ単一テーブルの折り返し」として設計し、複数ビュー化はしません(共有のトップヘッダ・全列共有の hostW/addrW/viaW・共有 scrollTop・列メジャー配置を不変に保つ)。

使い方

  • 操作: ] で列数を増やし、[ で減らす(最小 1 列)
  • 起動時既定: CLI -c / --split N、設定ファイル split N(CLI 明示 > config > 1)
  • 要求列数が端末幅に収まらなければ自動でクランプし、キー凡例に実効/要求列数(例 cols 2/3)を前方表示。m/v/p で統計列を隠すと 1 列が狭まり、より多くの列が入る
HOSTNAME  ADDRESS    LOSS RTT RESULT     | HOSTNAME  ADDRESS    LOSS RTT RESULT
gw        10.0.0.1     0%   1 ▁▂▁▁X▁     | web03     10.0.0.13    0%   4 ▃▂▃▄▂
ns1       10.0.0.2     0%   2 ▁▁▂▁▁      | db01      10.0.0.21    0%   9 ▆▅▆▇▆

設計上のポイント

  • 回帰安全: cols==1(デフォルト)は既存描画パスをそのまま通りバイト一致。effCols>=2 のみ新パス
  • 幅予算: 1 列の最小幅 minColumnWidth から effectiveCols を整数除算でクランプし、resW >= minResultWidth(結果バー桁溢れなし)を保証。要求 > 行数のときは行数でも頭打ち(幻の空列を防止)
  • scrollMetrics 一元化: 容量を perCol × cols のグリッドへ集約し、View(スライス)と scroll(クランプ)の単一の真実源を維持
  • ANSI 対応セル幅: 整形済みセルは lipgloss.Width で測定し、超過時は ansi.Truncate→再パディングで厳密に列幅へ。CJK 端末で曖昧幅にならない ASCII | をガターに採用

コミット構成

  1. feat(tui): 機能本体(model/view/styles、CLI フラグ、config split ディレクティブ、テスト、README/deadman.conf)
  2. fix(tui): 段組みセルの幅超過切り詰めと列数表示の切り捨て対策(1 巡目レビュー)
  3. fix(tui): 幻の空列・幅超過の再パディング・列数キーの暴走を修正(2 巡目レビュー)

テスト

  • make check(tidy-check / fmt-check / vet / lint / test)グリーン、make test-race 通過
  • 幅算術・scrollMetrics 容量・列メジャー配置(top>0 含む)・矢印の絶対インデックス・幅クランプ・空列頭打ち・セル切り詰め・凡例の切り捨て耐性などを internal/tui/split_test.go で検証

既知の制限

ブロックグリフ(結果バー)を曖昧幅=2 で描画する端末設定では、段組み時に列 0 のバーが列 1 へわずかにはみ出す可能性があります。これは結果バー全体が依存する既存の「グリフ=1 桁」前提であり、本 PR が導入した回帰ではありません(単列でも同前提)。

yuu61 added 4 commits June 10, 2026 22:15
端末の縦幅が足りないが横幅に余裕がある場合に、単一の監視リストを
新聞段組み(列メジャー: 縦→横)で複数列に折り返して表示できるようにする。
タイトル・キー凡例は全体で 1 つだけ共有し、tmux で複数インスタンスを並べる
方式と違いヘッダを重複させないため、節約したい縦行を浪費しない。

「同じ単一テーブルの折り返し」として設計し複数ビュー化はしない:
共有のトップヘッダ・全列共有の hostW/addrW/viaW・共有 scrollTop を不変に保つ。

- 操作: `]` で列数増、`[` で減(最小 1 列)
- 起動時既定: CLI `-c`/`--split N`、config `split N`(CLI 明示 > config > 1)
- 列数は端末幅に収まる範囲へ自動クランプ(effectiveCols)。結果バーは
  minResultWidth を下回らないことを整数除算で保証(桁溢れなし)。実効/要求列数を
  キー凡例に表示
- scrollMetrics の容量を perCol×cols のグリッドへ一元化(View と scroll の
  単一の真実源を維持)。cols==1 は既存パスにバイト一致で退化
- セル幅は ANSI 対応の lipgloss.Width で測定(結果バー予算のグリフ=1桁前提と一致)。
  列間ガターは CJK 端末で曖昧幅にならない ASCII `|`(幅一定)

README / deadman.conf を更新。tui/config/main にテストを追加。
コードレビュー指摘への対応。

- padCell: 長時間稼働で SNT/FAIL が 5 桁を超える(" %4d" が桁あふれ)と統計セルが
  ヘッダ予算より広がり、満杯の履歴バーと重なってセルが列幅を超える。従来は超過分を
  そのまま返していたため後続列が右へずれ端末幅を超過し、右端列の履歴が切り落とされて
  いた。ANSI 対応の ansi.Truncate でセル幅へ切り詰める(最右端=履歴バーの最古グリフ
  から削るので統計値は失われない)。x/ansi を direct 依存へ。
- keysLine: 実効/要求列数を凡例末尾に付けていたため、統計列を隠して狭い端末で 2 列に
  なるようなクランプ発生時にこそ Bubble Tea のレンダラーで切り捨てられ確認できなかった。
  "RTT Scale Nms." 直後(前方)へ移し、行末切り捨てに耐えるようにする。

split_test.go に padCell の伸縮・切り詰めテスト、SNT 肥大時に行が端末幅を超えない
ことの検証、クランプ表示が幅 80 への切り捨て後も残ることの検証を追加。
2 巡目コードレビュー指摘への対応。

- effectiveCols: 幅は収まるが「要求列数 > 行数で埋まる列数」のとき、ヘッダのみの
  空列が右端に出ていた。fits 時は perCol から ceil(rows/perCol) 列で足りるため、
  行数で頭打ちする。width/ヒント/レイアウトが一貫し、表示列は端末幅を満たす。
- padCell: ansi.Truncate は幅 2 グリフが境界に跨ると w 未満で停止するため、
  切り詰め後に必ず w へ再パディングする(次列の左ずれ防止)。
- adjustCols: ']' を effectiveCols()+1 で頭打ち。狭い端末での連打で要求値が際限なく
  増え '[' が無反応に見える問題を解消(統計列を隠せば上限が上がり増やせる)。
- separatorLine を separatorCell(m.width) へ委譲し、二重定義の乖離を防ぐ。
- リロードが split を保持する旨を README に追記(scale/precision と同方針)。

split_test.go に、行数による列数頭打ち(幻の空列なし)と、スクロール時(top>0)の
列メジャー描画の検証を追加。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd437273ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/tui/model.go Outdated
fit := (m.width + colGutterWidth) / (m.minColumnWidth() + colGutterWidth)
eff := max(1, min(m.cols, fit))

if usable := m.usableRows(); usable > 0 && len(m.rows) > 0 && len(m.rows) <= usable*eff {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clamp empty host lists to one column

When a valid config contains only directives/comments and no rows, this len(m.rows) > 0 guard skips the row-count clamp, so effectiveCols() remains width/request-clamped above 1 and renderColumns displays several header-only columns. This also occurs after reloading an temporarily empty config because ParseConfig and buildRows accept zero targets. Return one effective column when len(m.rows) == 0 to avoid the phantom columns this clamp is intended to prevent.

Useful? React with 👍 / 👎.

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

yuu61 added 2 commits June 11, 2026 06:28
これまで HOSTNAME と ADDRESS は常時表示の構造列で、トグルできるのは VIA と
統計列だけだった。両列を VIA と同じ visible マップに載せ、設定ディレクティブ
`columns HOSTNAME=off` / `ADDRESS=off` と実行中キー `h` / `a` で表示を切り替え
られるようにする。RESULT(結果バー)は deadman の看板のため常時表示のまま。

- columns.go: colHost/colAddr を追加し、structuralCols(HOSTNAME/ADDRESS/VIA の
  レジストリ)を新設。buildVisible が 3 列とも表示で seed する。
- view.go: headerLine/targetLine で HOSTNAME・ADDRESS を columnVisible でゲート
  (全列表示時は従来とバイト一致)。
- model.go: rowFixedWidth を view.go と同一条件でゲートし、結果バー幅 resW の
  整合を保つ。v 単独だった処理を h/a/v 共通の toggleStructuralCol に集約。
- 段組み(split)との相互作用: 両列を隠すと minColumnWidth が縮み、より多くの
  列が収まる。桁溢れ不変条件(colContentWidth >= minColumnWidth)は維持。
- README: 操作に h/a、列の表示節に受理キー HOSTNAME ADDRESS と「RESULT は常時
  表示で隠せない」を明記。

キー凡例には h/a を出さず簡潔に保つ(README に記載)。

テスト: h/a トグル、config 起動時非表示、ヘッダ幅↔rowFixedWidth の結合不変
条件(8 組合せ)、非表示で段組み列数が増える+桁溢れなし、を追加。
ディレクティブ/コメントのみで対象行が 0 件の config(または一時的に空の config
へのリロード)で、effectiveCols の行数クランプが len(m.rows) > 0 ガードに阻まれて
素通りし、幅/要求ベースの列数が 1 より大きいまま残っていた。renderColumns が
ヘッダだけの幻の列を複数描画する。空一覧は自明に 1 列なので早期 return のガードに
len(m.rows) == 0 を追加し、冗長になったクランプ側の len(m.rows) > 0 を削除する。

PR #9 レビュー指摘(codex P2)への対応。回帰テスト TestEmptyRowsClampToOneColumn
を追加。
@yuu61 yuu61 merged commit 65c2cba into master Jun 10, 2026
2 checks passed
@yuu61 yuu61 deleted the feature/split-view branch June 10, 2026 21:33
yuu61 added a commit that referenced this pull request Jun 10, 2026
コード全体のレビューで検出した論理・契約・セキュリティ上の問題を修正。
各修正にテストを追加し、make check / test-race / cross をすべて緑で確認。

ping:
- routeros: IPv6 中継ホストを URL でブラケットし、url.Parse 失敗(毎回 Failed)を防止 (#1)
- routeros: http.Client を pinger に保持して再利用、毎 Send の接続/goroutine リークを解消 (#2)
- routeros: verify を寛容な bool に。yes/1/on/不明値は secure 既定とし、フェイルオープンを解消 (#4)
- snmp/hping/routeros: 非対応な source= を起動時警告で案内(無音無視を解消、監視は継続)(#8)
- parse: snmp/hping の RTT 正規表現を小数対応に。sub-1ms の切り捨てを解消 (#6)
- parse: RouterOS min-rtt を s/ms/us の合算に。whole-ms ("5ms") の取りこぼしを解消 (#10)
- subprocess/osname: IPv6 の ping コマンドを os= 属性で選択(Darwin は別 ping6、他は現行 OS の統合 ping -6)。リモート実行コマンドをローカル LookPath で決める依存を解消 (#7)

config:
- インデントした "#" 行コメントを除去し、コメントアウトしたホストの誤 ping を防止 (#3)
- ディレクティブ語(columns/scale/precision/split)を大小無視の予約語に (#9)
- quote-aware な ";#" トレーラ除去で、引用符内の値中の ";#" を保護 (#11)

monitor:
- ログに up/down 状態と %.3f を導入。失敗時に直前成功の stale RTT を記録する問題を解消 (#5)

tui:
- 端末高さが固定ヘッダ未満のときヘッダがあふれてタイトルが落ちる問題をクランプで防止 (#15)

cmd:
- --help / -h を exit 0 に(従来は exit 2 + "flag: help requested")(#12)

docs:
- README の Go バージョン要件を go.mod の 1.25 に一致 (#14)
- source / verify の対応モードと綴り、予約ディレクティブ語の契約を正確化

(#13「凡例に h/a を追加」は撤回。簡潔な凡例は設計どおりで、h/a は README に記載済み)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant