2026-08-04_作業ログ
作業ログ 2026-08-04
13:20 - SCALE CRM v11.6.50: 架電リストの操作品質根治(大串FB・画面収録から・本番LIVE・git 17f7c37)
大串FB: 「架電リストを実際に動かした中で修正して欲しいのが何個もある」「画面が一瞬切り替わったり他の行にいったりカクってしたり。こういうところが品質悪いって感じる所以」+32秒の画面収録
動画の機械解析(推測せず実測した)
- ffmpeg のシーン検出で画面が大きく変わった瞬間を抽出 → 8.96s / 17.29s / 26.84s にモーダル暗転、10.05s / 18.85s に復帰
- 前後フレームを切り出して比較した結果:
| 時刻 | 状態 |
|---|---|
| 10.1s | 行が全消え・列見出しも崩壊(従業員規模/架電担当者が消える) |
| 10.6s | 復帰(約0.5秒の空白) |
| 19.0〜19.6s | 2回目。列見出しごと完全にまっさらが数秒継続 |
| 27.0s | 3回目も同様 |
真因(3つとも v11.6.47「常時サーバー駆動」で編集後の経路が旧方式のまま残っていたこと)
- 編集後
renderCallList()が#mainContentを丸ごと innerHTML で作り直す → 直後のfilterCL→/calls/queryの応答が返るまで tbody が空 renderCallList末尾のif(calls.length)filterCL()により、_cache が空だとサーバークエリ自体が走らない → 一度空になると数秒戻らない(2回目の症状の主因)filterCLのサーバー分岐が_clServerQuery(0)固定 → 編集のたび先頭ジャンプ。v10.78 の作り込んだスクロール復元は「再描画直後に行が存在する」前提なので、行ゼロでは無効化されていた
修正(A/B/C・大串GO)
- A:
_clPatchRowCells(callId)新設 — 取り消し後は該当行のセル+上部KPIだけ更新(全体再描画ゼロ) - B: 応答が返るまで前回結果を即描画(空白ゼロ)/
_clKeepOffsetOnceで編集後は表示位置維持/サーバー駆動時は _cache が空でも必ずクエリ実行/「データなし」の誤表示を抑止 - C: アポ取り消し時、他スロットにアポ系が残っていなければ
apoDate/apoGetterもクリア
アポ取り消しの調査結果(大串の懸念「取り消してもアポとして反映されてるかも」)
- 集計は正しい。全経路(ダッシュ/日報/レポート/メンバー成績/タイマー)が
_clApoCountDate(callNStatus 基準)を通っており、取り消せばアポ数・アポ率・API から外れる。動画のKPIでもアポ獲得は1のまま増えていない - D1 の残骸(ステータス空 + apoDate 残り)も 0件
- ただし
apoDate/apoGetterがフィールドとして残る穴は実在 → C で修正
D(ステータス選択時の全画面モーダル)= 現状維持で決定(2026-08-04 大串判断)
右サイドパネル化・行内完結の2案を提示したうえで「現状維持でいい」。記載漏れ防止の仕様(閉じられない・毎回まっさら)はこのまま。今後この件を蒸し返さない。
- 出るのは
ST_REQUIRED_FIELDSに項目があるステータスのみ。担当不在(2項目)/失注(3)/育成(5)/資料請求・メール日程調整(6)/アポ獲得(7)。コールのみ・不通・受付ブロック・架電NG・NGリスト・重複はモーダルなし - 「閉じられない」「毎回まっさら」は過去の大串FB(v9.184 記載漏れ・誤送信防止)で意図的にそうなっている
- 提示した選択肢: ①右サイドパネル化(リストが見えたまま=暗転ゼロ)②項目が少ないステータスは行内で完結 ③現状維持
学び
- 動画FBは推測で読まずフレームを機械抽出して実測する。ffmpeg のシーン検出で「変化の瞬間」を出し、前後を並べると真因の当たりが一発でつく(体感の言語化に頼らない)
- 新方式に移行したら「旧方式の前提で書かれたガード」を全部洗う。
if(calls.length)のような一見無害な条件が、新方式では「永久に空」を生む。v11.6.49(案件切替)と全く同じ構図が編集経路にも残っていた - スクロール復元のような作り込みは「行が存在する」前提。データ供給が非同期になると静かに無効化される
13:40 - v11.6.51 / v11.6.52(大串FB・本番LIVE・git feaa5fd)
v11.6.51 使い勝手3点
- 担当者の自動変更トーストを全廃(「架電担当者を『◯◯』→『◯◯』に自動変更しました」)。大串「どんなときも表示させないでOK」。自動変更の挙動と監査ログは維持(core.js 2箇所・calllist_status.js 1箇所)
- time/date のピッカーアイコンが黒地に黒で不可視 →
::-webkit-calendar-picker-indicatorをfilter:invert(1)で反転。架電リストだけでなく全画面の日付/時刻入力に横展開 - 行ホバー色が「企業名・電話番号」だけ乗らない(色が途中で切れる)。真因=左固定列(チェック/#/企業名/電話番号)が JS 側で
backgroundをインライン指定しており、CSS のtr:hover td(style.css:144) が負けていた →#clTB tr:not(.cl-vspacer):hover tdに!importantで全セルに適用
v11.6.52 ファビコン透過刷新
- 指定の翼マーク(
~/Downloads/生成画像1 (20).png・1254px)から白背景をアルファ化(輝度→alpha)→ bbox トリム → 8%余白 → 32/64/180px 生成 - 旧ファビコン(黒背景+白マーク)は
~/scale-lead-backups/favicon-pre-v11651/に退避 - 本番 favicon-32.png の md5 一致を確認
- 注意: マークは黒のまま透過なので、タブがダーク配色の環境では見えにくい可能性あり(未指示のため現状のまま)
13:45 - 【調査】架電リストの読み込み速度(大串「どうしても上げれないもの?」)
実測(D1 で boot クエリを再現)
| 対象 | 実測 |
|---|---|
| app_data 全体 | 839キー / 4.25MB |
| 起動時に実際に運ぶ量(boot=1 除外後) | 829キー / 2.02MB |
起動ペイロードの約70%が「廃止済み機能のデータ」
| キー | サイズ |
|---|---|
| proj_*_voice_mobile_log × 5案件 | 計 約1.4MB |
| backup_2026-04-23 / 04-24 | 計 166KB |
voice_mobile_logは Zoomphone連携が2026-05-20に廃止済みで誰も読んでいない。にもかかわらずBOOT_EXCLUDE_LIKE(functions/api/data/index.ts:17)から漏れており、全員が毎回の起動で1.4MBをダウンロードし続けている- コメントには「2026-05-20: voice_mobile_log / voice_recordings 削除」とあるが、実際は除外リストから消しただけでD1にはデータが残っていた(=除外されなくなった)
改善余地(未着手・GO待ち)
- 即効: voice_mobile_log と backup_ を boot 除外 → 2.02MB → 約0.5MB(70%減)*。データは消さないので安全(D1掃除は別途)
- 構造: 起動が「全部取り終えてから画面を出す」直列(
await fetch('/api/data?boot=1'))。画面を先に描いてデータを後追いにすれば体感はさらに短縮
学び
- 「除外リストから消す」と「データを消す」は別物。機能廃止時に除外リストだけ整理すると、残ったデータが除外対象から外れて逆に毎回運ばれる状態になる(今回1.4MB/人/起動)。廃止時は「データ削除」「除外維持」のどちらかを必ず選ぶ
13:50 - SCALE CRM v11.6.53: 起動を速く(大串GO「1.2両方やる」・本番LIVE・git 195dfce)
【1】起動時に運ぶデータを76%カット
BOOT_EXCLUDE_LIKE(functions/api/data/index.ts)に%_voice_mobile_log/%_voice_recordings/backup_%を追加- 実測: 829キー/2.02MB → 823キー/0.49MB(76%減)
- 内訳: voice_mobile_log 5案件で約1.4MB=全体の約70%。Zoomphone連携は2026-05-20に廃止済みで、現行コードの参照はコメントのみ(grep で読者ゼロ確認)
- D1 のデータは残置(復元可)。運ばないだけ
【2】画面の先出し(boot 待ちの解消)
- 従来:
showApp()の後、await _initSupabase()(/api/data?boot=1)が終わるまで中央が「読み込み中...」=D1応答 0.5〜0.8秒を丸ごと待っていた - 変更:
D()が_cache不在時にlocalStorage(sb_*)を読む性質を使い、前回データで本画面を先に描画。boot は裏で並行取得し、完了後に既存の再描画が最新へ差し替える - 架電リストは Phase5 でサーバー駆動=boot(app_data) 非依存なので、boot と行取得が並列になる
- 前回データが無い(初回ログイン/別PC)ときは先出しをスキップ=従来動作(安全側)
学び(恒久)
- 機能を廃止したら「データを消す」か「boot除外を維持する」かを必ず選ぶ。除外リストからパターンだけ消すと、残ったデータが除外対象から外れて毎回全員に配信される。今回それで1.4MB/人/起動を2ヶ月半運び続けていた
- 「重い」の調査はまずペイロードの中身を実測。2.02MBの内訳を見た瞬間に、7割が使われていないデータだと分かった(コードを読む前にデータを見る)
- 起動シーケンスは「画面を出す」と「データを取る」を直列にしない。ローカルキャッシュで先に描いて、通信は並列にする(架電リストの案件切替 v11.6.49 と同じ設計思想)
14:00 - 【調査】同種の問題の横断洗い出し(大串「同じ問題起きる可能性ない?未然に防ぎたい」)
共通の真因パターン
「_cache = 案件の全行」という前提で書かれたコードが、Phase5(サーバー駆動=_cacheは表示分のみ)に移行した後も残っている。
v11.6.49(案件切替)・v11.6.50(編集後)はこのパターンの2例。他にも残っていないかを機械的に洗った。
影響範囲の前提(D1実測 2026-08-04)
| 案件 | 行数 | 全量ロード |
|---|---|---|
| mnpg72otcfqhu(結グループ本体) | 17,321 | しない(8,000行超) |
| mr8u1qi3oy490 | 4,481 | する(v11.6.49でアイドル化) |
| moz6qooo63107 | 3,348 | する |
| mpgfeuixht619 | 450 | する |
→ 8,000行超は1案件のみ。大案件限定の問題は結グループ本体だけで起きる。
見つかったリスク
【A】全案件で発生・明白なバグ — フィルタUIが操作不能になる
- calllist_core.js:906-915 の検索ボックス/全ステータス/全担当者/全地域/全セグメントが ${!calls.length?'disabled':''}
- サーバー駆動で _cache が空の間(案件切替直後・v11.6.49でアイドル化したぶん数秒)、行は表示されているのにフィルタが灰色で押せない
- さらに地域(_clPrefOptions(calls))とセグメントの選択肢を _cache から生成しているため、空だと選択肢もゼロ
【B】大案件(結グループ 17,321行)限定
1. CSV出力が全件出ない — calllist_csv.js:29 が PD('call_list') 依存=_cacheの200行+今日行のみ
2. CSV取込の重複判定が漏れる — calllist_csv.js:248 existingCalls=PD('call_list')。既存行が_cacheに無いと重複を検知できず二重取込になる(データ品質に直結。v11.6.31で分配経路は「サーバー正本で判定」に直したが、CSV取込は未対応)
3. 分析レポート/ダッシュボード/タイマーKPIが過少集計 — いずれも PD('call_list') 全走査(report:1 / dashboard:3 / timer:2 箇所)。※日報だけは v11.6.48 でガード済み
【C】守られていた点(触ってはいけない)
- calllist_core.js:35,237 の if(!calls.length)return; は 空配列を PS すると per-row diffPush が全行 HARD DELETE する事故の防止ガード(v11.5.47)。これは「_cache前提」ではなくデータ保護なので残す
対処案(GO待ち)
- A: サーバー駆動時は disabled を外す+選択肢はサーバー(facets)or 固定リストから生成 → すぐ直せる
- B-2: CSV取込の重複判定をサーバー正本へ(v11.6.31 分配と同型)→ データ品質最優先
- B-1: CSV出力はサーバーから全件取得して書き出し
- B-3: stats API(/api/calls/stats・v11.6.47で新設済み)をUIに接続 = Phase5.4 の残タスクそのもの
14:55 - SCALE CRM v11.6.54: 洗い出した3つの穴を塞ぐ(大串GO「1〜3やって」・本番LIVE・git 30ea9df)
【B-2 最優先】CSV取込の二重取込を根治
- 新API
functions/api/calls/dedup.ts: 照合キー4項目(SalesNowリンク/会社名/最新ステータス/架電担当者)だけを仮想生成列から返す軽量API。配列の配列で転送量を圧縮・truncatedフラグ付き processImportCSV: 大案件 or_cache空のときサーバー正本と照合してから取り込む_cache配列そのものに push すると偽行が保存経路に載るので、必ずslice()のコピーに補完(事故防止)- 照合失敗時は「二重に入る可能性がある」と confirm で明示(黙って取りこぼさない)+取込結果に照合件数を表示
- 認証:
_middleware.tsは/api/calls前方一致でカバー済み=列挙追加は不要(地雷を踏まずに済んだ)。本番 403 は未認証時の正常応答
【A】フィルタが操作不能になる問題
- 検索/ステータス/担当者/地域/セグメントの
disabled条件!calls.lengthを_fdisに集約し、サーバー駆動時は無効化しない(5箇所を一括置換) _clPrefOptions:_cacheが空で実在県を拾えないときは全都道府県にフォールバック(選択肢ゼロを作らない)
【B-1】CSV出力が全件出ない問題
exportCallCSVを async 化し、大案件はサーバーから全件取得してから書き出し- 件数明示の confirm + 進捗トースト + 二重実行ガード(119k分配インシデントの教訓を適用)
- 取得失敗時は「一部だけになる」と明示して確認
【C】触っていない(役割が真逆のコード)
calllist_core.js:35,237のif(!calls.length)return;は空配列を PS すると per-row diffPush が全行 HARD DELETE する事故の防止(v11.5.47)。見た目は「_cache前提」と同じ形だが役割が正反対なので維持
学び(恒久・横展開)
- 同じ形のコードでも役割は真逆のことがある。
!calls.lengthは「UIを止める誤ったガード」と「データ全消しを防ぐ正しいガード」の両方に使われていた。機械置換の前に1件ずつ役割を確認する - 既存配列(_cache)に補完データを push しない。判定用に足すつもりでも保存経路に載れば偽行としてD1に書かれる。必ずコピーを作る
- 影響範囲は先にデータで確定させる(8,000行超は1案件のみ→「大案件限定の問題」の優先度を正しく判断できた)
16:20 - SCALE CRM v11.6.55(Phase5.4 / B-3): 全期間を走査する画面の取りこぼしを根治(大串GO・本番LIVE・git d3310f2)
調査で当初の見立てを訂正した
14:00 の洗い出しでは「分析レポート・ダッシュボード・タイマーKPIが過少集計」と報告したが、コードを精読したら実態は違った:
- 架電リスト上部KPI(_collectTodayStats)・Slack中断報告・_clTallyByList は 当日のみの集計 → v11.6.45 の _clServerPrefetchToday(today_called 500件)で大案件でも既に正しかった
- 個人ダッシュボードは daily_teleapo(日報)ベースで、call_list 全走査ではなかった
- 実際に取りこぼすのは「全期間を走査する画面」= アポ獲得リスト(getAutoAppointments)/商談予定/再架電リマインダー(renderReminders)/クロスチャネル重複チェック
方式の選択(ここが肝)
- 集計ロジックのSQL化は不採用。通電判定が画面ごとに違う(
report.jsは CONNECTED_ST=担当不在を除く /timer.jsは担当不在を含む)。SQLに寄せると数値が変わる=退化リスクが高い - D1実測で アポ系9件・再架電予定469件=計478件と少数と判明 → 「重要行だけ先読みして _cache に入れる」方式を採用。既存の集計ロジックを1行も変えずに正しくなる
実装
query.ts: presethas_recall追加(日付を問わず recallDate を持つ行)。JS述語String(recallDate).trim()と SQLTRIM(COALESCE(c_recall,''))<>''を厳密1:1・earlyに登録。期待件数469をD1で照合済みcalllist_core.js:_clServerPrefetchKeyRows()新設。大案件のみapo/has_recallを先読みし_clServerBridgeRowsで_cacheへ。表示を優先して_clIdle(2秒)で後追い実行- 作りかけの
tally.ts(集計SQL化API・1.2秒/8万行スキャン)は不採用のため削除(未使用APIを本番に残さない)
学び(恒久)
- 「集計が合わない」時、集計側を書き換える前に"データ供給側"を直せないか考える。ロジックを触ると画面ごとの微妙な差(通電判定など)を壊すが、データを揃えるだけなら退化ゼロ。今回 478件の先読みで4画面が同時に直った
- 報告した見立ては実装前に必ず精読で裏を取る。「レポート/ダッシュボード/KPIが過少」は精読すると大半が既に担保済みで、真の対象は別だった(実装してから気づいたら数値を壊していた)
- 作ってしまった不採用コードは残さず消す(未使用APIは負債)
17:35 - SCALE CRM v11.6.57: 起動時「読み込み中...」を根治(大串FB・本番LIVE・git 8572b06)
真因(推測せず error_log の実データで確定)
ReferenceError: renderManualPage is not defined
at renderPage → showApp → _appBoot
renderPage冒頭の R マップがmanual:renderManualPage/knowledge_hub:renderKnowledgeHubをガード無しで直参照していた- この2関数は kickoff.js 所属=v11.6.32 の遅延ロード対象なので、起動時点では未定義
- オブジェクトリテラルを作る時点で落ちるため、開いているページに関係なく
renderPage全体が失敗 → catch(core.js:5843)が_loadingPlaceholderHTML()を表示 → boot 完了まで「読み込み中...」 - error_log に 大串・山本の両方で同一スタックを記録。2026-07-27(v11.6.32)以降ずっと全員が毎回踏んでいた
なぜ漏れたか
v11.6.32 で「R マップを typeof ガード化」した際、同じ kickoff.js の renderKickoff / renderOnboarding / renderPartnerOnboard はガード+_LAZY_PAGE_MOD 登録をしたのに、renderManualPage と renderKnowledgeHub の2つだけ両方とも漏れた。
修正
_LAZY_PAGE_MODにmanual:'kickoff'/knowledge_hub:'kickoff'を追加 → 未ロード時は既存機構(renderPage:6578)が kickoff を遅延ロードして再描画する経路に正しく乗る- R マップの未ガード直参照 25個すべてを
(typeof X==='function'?X:null)に統一(同じ事故の再発防止)
前段(v11.6.56)でやったこと=正直な訂正
- v11.6.53【2】の「画面の先出し」は showApp() が既に同じことをしていた(core.js:5837-5839)ため完全な重複=効果ゼロ → 撤去
- 代わりに
_reportBootRenderFail()を仕込み、起動時の描画失敗を1セッション1回だけ/api/error→error_logに自動記録。これで大串に手間をかけずに真因を特定できた - boot の D1 クエリは実測 32ms(825キー返却)=待ち時間の主因ではないことも確定
学び(恒久・最重要級)
- オブジェクトリテラル内の関数直参照は「1個の未定義」で全体を道連れにする。遅延ロードを導入したら、そのファイルが持つ全てのエクスポートを洗い出してガード+登録する(1つでも漏れると全ページが死ぬ)
- 再現しづらい不具合は「自動記録」を仕込むのが最短。コンソールを見てもらうラリーをせず、error_log に1件送るだけで確定した。しかも他メンバーの発生状況まで分かった
- 体感の悪さを「遅い」と決めつけない。今回はデータ取得32msに対し、実体は「毎回エラーで描画が止まっていた」。速度改善(v11.6.53)を積む前にエラーを疑うべきだった
21:00 - SCALE CRM v11.6.58: Slack投稿失敗の自動記録+稼働時間の矛盾警告(大串GO・本番LIVE・git 6ecd9c6)
発端: 大串「稼働終了だけSlackで上がってくるのはなんでだろう?」
スレッド(結グループ_エアコン_テレアポ支援_作業報告 / 08-04 山本さん)に 「✅稼働終了」だけ1件で「▶稼働開始」が無かった。
調査で分かったこと(当初の推測を1回訂正した)
- Slackスレッドに出るのは 稼働開始 / 作業中断 / 稼働終了 の3種類
endCallTimerは 稼働開始を押していなくても実行できる(1分未満なら確認ダイアログのみ)- 当初「開始を押していない」と回答したが誤りだった。D1 の
daily_teleapo_rowsを見るとnote:'タイマーから記録'/workHours:0.09。_timerStateは日付変更時に前日ぶんをtimer_missed_end_reportへ退避してelapsed=0から始まる仕様なので、この0.09h(5.4分)は当日ぶん=開始は押されている - → 開始通知だけが投稿に失敗した可能性が高い(同じ日に前野さんは4件投稿されておりシステム全体の障害ではない)
- 従来は
slackPostの失敗が console 止まりで後から追跡できなかった
実装
【1】Slack投稿失敗の可視化
- _reportSlackFail(where,detail) 新設(pagecore_slack.js)→ /api/error → error_log に記録。1セッション5件上限
- 設置箇所: 稼働開始(前提なし/スレッド取得失敗/投稿失敗/例外)・作業中断(投稿失敗)・稼働終了(スレッド取得失敗/投稿失敗)
- slackPost は成功時のみ {ok:true} を返す仕様なので !(res&&res.ok) で判定
【2】実態とズレた日報の防止
- endCallTimer に sanity check: 架電5件以上 かつ CPH>60 なら警告して確認
- 警告は clearInterval より前に配置 → キャンセル時はタイマーが止まらず、そのまま作業に戻れる
- 8/4実例: 稼働0.09h(5分)で架電8件=CPH88.9 → 稼働時間・CPH・API・ランク・推定時給が全てズレたままSlack報告されていた
学び
- 「押していない」と断定する前にデータで裏を取る。タイマーの仕様(日跨ぎで退避してリセット)まで確認して初めて「押している」と分かった。1度誤った回答をしてすぐ訂正した
- 握りつぶした失敗は必ず後で調査不能になる。Slack投稿は失敗しても業務が止まらないので握りつぶす設計だったが、その結果「なぜ出ていないか」が誰にも分からなかった。握りつぶすなら記録は残す
- 確認ダイアログは「キャンセルしたときに安全な状態か」まで設計する(今回はタイマー停止前に置くことで、キャンセル=稼働継続にした)