• #Vue
  • #TypeScript
  • #UI
  • #ハイライト
  • #CSS
開発eurekapu-nuxt4

CFWS v2のセル単位ハイライト + 全navigate型への横展開

CFWS(キャッシュフロー精算表)の「税引前当期純利益」をクリックすると、年次推移表のFY202603の該当セルにジャンプする。FY202603の税引前セルがオレンジ枠で囲まれ、scrollIntoView で画面中央に滑り込む。同じ仕組みを年次推移表・財務シート・投資シート・株式発行シートの全navigate型に展開した。途中で「建物と備品が別々にハイライトされない」問題を踏み、複合キーの優先順位を逆転させて解消した。CSS specificityの罠も2回踏み、最後に simplify をかけて共通composablesに整理した。

モデルケース: 「税引前当期純利益」のセル単位ハイライト

最初のモデルケースとして、CFWSの「税引前当期純利益」リンクから年次推移表へ飛ぶ動線を組んだ。

CfItemAction 型に highlightYearIndex を追加し、CFWSの年度クリックと連動できるようにした。

// app/types/cashflow-statement.ts
export interface CfItemAction {
  navigate: { type: NavigateType; key?: string }
  highlightAccountKey?: string
  highlightYearIndex?: number  // 追加
}

V2AccountingSheet側で highlightYearIndex を受け取り、対応セルにクラスと ref を付与する。

<td
  :ref="(el) => setCellRef(el, year, '税引前当期純利益')"
  :class="{ 'cell-highlight': isHighlighted(year, '税引前当期純利益') }"
>
  {{ fmt(income) }}
</td>

ブラウザで叩くと、FY202603の▲2,113,770セルがオレンジ枠で囲まれ、scrollIntoView({ block: 'center' }) で画面中央に揃った。テストも17件追加して79/79 pass。

試行錯誤1: 建物と備品が同じセルにハイライトする

ここでユーザーから指摘が入った。「建物の減価償却費(150万円)と備品の減価償却費(12万円)を別々にクリックしても、合計行の162万円にハイライトが飛んでしまう」。

スクリーンショットを2枚並べて見ると、確かに両方とも「合計」セルにオレンジ枠がついていた。

原因1: 単独キーが先に拾われる

cfItemActions'減価償却費' という単独キーで登録されていた。建物クリックでも備品クリックでも、このキーが最初にヒットしてしまい、複合キー('資産名:列名' 形式)にたどり着かない。

複合キーを後から追加して cfItemActions['減価償却費:建物'] を登録しても、CfWorksheetTable.handleCfItemClick 側の参照順が「単独キー → 複合キー」のままだったため、依然として単独キーが先に拾われていた。順序を逆転させて解決。

// Before: 単独キーが先
const action = cfItemActions[cfItemName] ?? cfItemActions[`${cfItemName}:${bsAccount}`]

// After: 複合キーが先
const action = cfItemActions[`${cfItemName}:${bsAccount}`] ?? cfItemActions[cfItemName]

原因2: cfSourceTableから親科目を拾えていない

buildV2CfItemActions の中で、cfSourceTable の row だけを見て複合キーを生成していた。ところが row には 減価償却累計額 しか入っておらず、建物 備品 のような親科目(tangible 系)が抽出できない。tangibleAccounts を別途引き、親科目名で複合キーを組み立てる方式に変更した。

const tangibleAccounts = bsAccounts.filter(a => a.kind === 'tangible')
for (const asset of tangibleAccounts) {
  cfItemActions[`減価償却費:${asset.name}`] = {
    navigate: { type: 'investment', key: asset.name },
    highlightAccountKey: `${asset.name}:減価償却`,  // 行+列の複合キー
    highlightYearIndex: i,
  }
}

V2InvestmentSheet側では、'資産名:列名' 形式の highlightKey をパースして「行のセル単位」でハイライト判定する。

<td :class="{
  'cell-highlight': year.index === highlightYearIndex
    && `${assetName}:${columnName}` === highlightAccountKey
}">

ハードリロード後にURLが ?ihl=建物:減価償却&ihy=1 に変わり、建物行の▲1,500,000セルだけがオレンジ枠に。備品クリックも▲120,000のセル単独でハイライトされた。合計行(▲1,620,000)には何もつかない。テストは86/86 pass。

CSS specificityの罠(2回踏んだ)

CFWSとは別の話だが、同日に2回踏んだので記録する。

1回目: 文字色が緑にならない

CF計算書の年次推移ビューで、navigate可能な項目(他シート参照リンク)の文字色が緑にならない。マウスオーバーした瞬間だけ緑に変わる。

DevToolsで確認すると、.cf-table td { color: #000000 }.navigable { color: #008000 } を上書きしていた。

セレクタspecificity
.cf-table td(0,1,1)color: #000000
.navigable(0,1,0)color: #008000

specificityが (0,1,1) > (0,1,0) なので黒が勝つ。:hover 擬似クラスがつくと specificity が上がるので、マウスオーバー時だけ緑に変わるという奇妙な挙動になっていた。

セレクタを .cf-table td.navigable (0,1,2) に上げて修正した。

2回目: 数値が右寄せにならない

同じ犯人で、.cf-table td { text-align: left }.num { text-align: right } を上書き。.cf-table td.num に書き直して解決。

CSSの specificity は頭で覚えていても、実装中は油断する。.table td のような共通スタイルを書いた瞬間に、後付けの修飾クラスが効かなくなる。

全navigate型への横展開

モデルケースが固まったので、finance investment equity accounting の全navigate typeに同期 ref を [...slug].vue で実装した。

  • finance: 借入シートは1行集約のため、highlightYearIndex だけ追加して列ハイライトと年度連動
  • equity: 専用 ref を作らず store.setYearIndex を直接呼んで簡略化
  • investment / accounting: モデルケースと同じ複合キー方式

借入金返済をクリックすれば finance/FY202703 にジャンプし、株式発行をクリックすれば equity の対応年度セルにジャンプする。

simplifyリファクタ

横展開後、/simplify で共通化した。3つのレビューエージェントを並列で走らせ、重複度の高い問題から潰す。

  • 共通フォーマッタ(金額・パーセント表示)を抽出
  • 複合キーの encode/decode を composables/useCellHighlight に集約
  • composables(useCellScroll, useYearSync)を新設し、5つのシート(V2AccountingSheet / V2InvestmentSheet / V2FinanceSheet / V2EquitySheet / V2CfStatementSheet)を共通化
  • buildV2CfItemActions の signature を変更し、tangibleAccounts を1回だけ計算

テスト 86 件 pass を維持。コミット 2294e3d に15ファイル(+622 / -275行)を記録した。

ミラーカラムレイアウトへの統合

夕方になって追加要望が出た。CFWSの年度切替が「FY202603 / FY202703 / FY202803 / FY202903 / FY203003」のボタン形式になっていた。これをミラーカラムレイアウトの第3カラム(年度選択列)に統合し、矢印キーで年度を移動できるようにしてほしい、とのこと。

globalIndex を更新して worksheet 専用の年度サブアイテムを追加し、worksheetYearIndex で同期させた。NavEntry 型にも worksheetYearIndex を追加して currentNavIndex / goTo を更新。V2WorksheetSheet.vue から年度ボタンを削除して完了。

これで矢印キーだけで年度を切り替えられるようになり、CFWSの見た目もすっきりした。

scheduled_tasks.lock のコミット事故

途中で .claude/scheduled_tasks.lock をコミットに含めかけた。ScheduleWakeup で作られた古いロックファイルで、Claude Code 内部のもの。削除して .gitignore に追加した。

学びメモ

  • 複合キーで参照する設計に切り替えるときは、呼び出し側の参照順も同時に逆転させる。片方だけ直しても、結局単独キーが先に拾われて意味がない
  • CSS specificityは頭で計算する前にDevToolsの「Computed」タブを開く方が早い。:hover で値が変わる現象が出たら specificity 戦争のサイン
  • 同じ作業を5シートに展開するときは、最初の1つを終わらせてから簡素化するのではなく、横展開した直後に simplify をかけると composables の境界が見つけやすい
  • HMRが効かない場面(ローン編集UIなど state を持つコンポーネント周辺)では、ハードリロードを早めに試す。「修正した気になっていた」事故が減る