# instinct: バニラJSダッシュボードで状態表示要素を追加する時、全state変化点を網羅し、display:noneされる親要素に依存しない独立要素に出す
vvv ダッシュボード(`static/js/dashboard/features/display.js`・無限スクロール型)に件数バッジ・末尾表示等のフィード状態UIを追加する時の勘所。Codex敵対レビューで2バグを発見した教訓(#538/PR#545)。
## state変化点を網羅する(漏れがバグになる)
`loadedCount`・`hasMore` を変える箇所は複数ある。1つでも `updateFeedStatus()` 呼び出しが漏れると、その経路で表示が更新されない:
- `resetGrid()`(リセット時) / `loadVideos()`末尾(通常ロード) / 初期データ箇所(`initialVideosData`) / **`prefetchNextBatch()`**(デスクトップのprefetch経路でも `hasMore=false` する)
- 特にprefetch経路は見落としやすい(別関数で非同期に hasMore を倒す)
## display:noneされる親要素に状態表示を入れない
末尾「すべて表示しました」を `#loading`(ロード完了で `loadingIndicator.style.display="none"` される)の中に入れると、**親ごと隠れて見えない**。→ 独立要素(`#feed-end` を sentinel の後に置く)を作り `style.display` で出し入れする。
## 関数巻き上げ(hoisting)を使えば定義位置に縛られない
`function updateFeedStatus(){}`(関数宣言)は巻き上げされるので、ファイル内のより前(prefetchNextBatch=91行)からでも呼べる。`const fn = () => {}` だと巻き上げされないので注意。
## トースト拡張は後方互換で
`showErrorToast(message, onRetry?)` のように第2引数オプショナルにし、既存の単一引数呼び出しを壊さない。再試行ボタンのリスナーは `cloneNode` で置換してから再登録し**多重登録を回避**・`autohide:false` で操作待ち。動的テキストは全て `textContent`(XSS防止)。
## 検証(Playwright切断時の代替)
- `node --check` で構文・`make dev`(vvv-dev:18xxx)起動→`curl /dashboard | grep '要素id'`・`curl /static/js/... | grep '関数名'` で**HTML要素とJS配信を実証**
- **別モデル(Codex)に敵対レビューさせる**と視覚確認できない論理バグ(表示漏れ・経路漏れ)が出る。初版2バグはこれで発見
## 関連
- [[instinct-pm2-orphan-port-held-drift]] — フロント反映のvvv-api再起動は起動可否を事前確認
- [[ultrareview-workflow]] — 実装(Claude)とレビュー(Codex)を別モデルに分ける敵対的検証
- vvv#538/PR#545
---
(migrated from local memory/instinct_vanilla_js_feed_status_hoisting.md)
instinct: バニラJSダッシュボードで状態表示要素を追加する時、全state変化点を網羅し、display:noneされる親要素に依存しない独立要素に出す