コンテンツにスキップ

システム品質評価(2026-09)

禹さん指示「システムの品質を全体的に高めたい。UI/UX と情報設計を見直したい」の第一歩として、 現状を数字で出す。所感ではなく実測を置く。

測定条件

  • 測定日: 2026-09-19
  • 測定対象: 04287d13(develop)
  • L1(docs 情報設計)は初版で 04287d13 時点の集計と #881 後の記述が混在していたため、 f805c1b5(develop)に統一して取り直した。本節 L1 の数値はすべてこの commit 時点である。 L2 のルート・導線の数値と L4 の数値は 04287d13f805c1b5 で同一であることを確認済み (ルート 83・到達可 66・クリック数 4/32/30・nav 区分 30/10/26。 この間のルート・nav 設定・apps/web/src/App.tsx への変更は無く、 L2/L4 対象の差分は配車フックの同名テスト追加 1 件のみ)。 ページファイル数の内訳(229 件・非ルート 157・テスト 95)は 04287d13 時点の歴史的値で、 f805c1b5 では当該テスト 1 件増により 230 件・非ルート 158・テスト 96 になる。 L2 の導線表(ルート・到達可否・クリック数)は両時点で変わらないため書き換えない。
  • 実測コマンド: grep -rhoE 'path="[^"]+"' apps/web/src --include='*.tsx' | sort -u | wc -l → 83、 git ls-tree -r --name-only 04287d13 -- apps/web/src/pages | grep -c '\.tsx$' → 229 (04287d13f805c1b5 に替えると 230。node scripts/app-ia-report.mjs --json の head 出力も routeFiles 72・imported 61・testFiles 96・unimported 1=計 230 件・非ルート 158 件)
  • 機械部分の再実行: node scripts/app-ia-report.mjs --json (ルート一覧・nav 到達可否の素・被 import 0 ページ。クリック数とチャネル判断は本書の表)

数え方の定義

最短クリック数はチャネル入口(ログイン直後の着地画面。実コードの resolveLandingPath を読んで決めた。 事務 /collector・ドライバー /driver・排出事業者 /discharger・委託先 /external-partner/settlements) からの最小クリックである。

測定はデスクトップ幅(md 以上・サイドナビが可視)に限る。 SideNav.tsx:43hidden … md:flex で、モバイル幅ではサイドナビが出ない。 モバイルは下部タブ 4 件+「その他」のボトムシート(BottomTabBar.tsx:18-19)になり、 overflow 側の画面はシートを開く操作が 1 つ増える(例: /discharger/collectors は 下部タブに出ず「その他」→「処理業者」で 2 クリック、詳細は 3 クリック)。 本表の数値はモバイルには当てはまらない。 モバイルの集計は未実施(後続)。

サイドナビ項目の選択は 1 クリックとし、 サイドナビのグループ見出しは表示専用(展開操作は要らない・SideNav.tsx 実測)のため数えない。 ハブ画面上部の SectionTab・設定タブ・ホーム共通タブの切替は 1 クリック、 一覧から詳細(:id)への遷移は一覧到達数に +1 する。リダイレクト(Navigate 互換維持)は 利用者のクリックを要しないため 0 とし、到達先の行で数える。status: 'soon'(準備中)は nav から到達不可とする。事務チャネルの数値は admin ロールで数えた (normal との差は下記「normal ロールとの差分」節に実測で示す)。

要約

4 層すべての実測が揃った。重点の候補は L4(実装の複雑さ)と L1(docs 情報設計)。 L2(アプリ導線)はデスクトップの最短到達クリック数に大きな深さが見られず、 L3(画面の質)は指定した静的規約の検出違反が 0 だった。 ただし L2・L3 の使いやすさ(視認性・作業達成率・所要時間・エラーからの回復・ キーボード操作・モバイル幅)は測っていないため、本書は投資不要を断定しない。 L4・L1 を先に調べる提案は仮説として扱う。 層ごとの結論と着手順は末尾の「4 層を通した結論」にある。

L2 の実測(本文の中心)

  • ルート 83 本の内訳: 到達可 66(0 クリック 4=各入口 / 1 クリック 32 / 2 クリック 30・3 クリック以上は 0デスクトップ幅の測定。モバイルは下部タブの overflow で増える——下記「数え方の定義」)、 互換リダイレクト 8、到達不可 4、ログイン前・入口 4、404 用 * 1。
  • nav 到達の内訳(到達可 66): サイドナビ直下 30 / ハブ内タブ 10 / 画面内リンク 26。
  • nav から辿れないのは 4 件(下記)。被リンク 0(URL 直打ちのみ)は /settings/notifications の 1 件。
  • ルートを持たないページファイル 157 件(04287d13 時点。f805c1b5 では (b) が 96 で 158 件)の内訳: (a) 他ページから import 61 / (b) テスト・ストーリー用 95 / (c) どこからも import されていない 1(JwnetWebEdiPage.tsx・旧 Web-EDI 死蔵画面。消さない)。
  • チャネルは 5 区分になった(指示書の 4 区分に収まらない 1 件あり。下記)。

L1 docs 情報設計

測定 commit: f805c1b5(develop)。初版は 04287d13 時点の集計と #881 後の記述が混在し、 nav ユニーク数もどちらの時点でも再現しない値(83)だったため、本節の表・本文はすべてこの commit で取り直した。

書かれた文書の 7 割はサイトから見えない

対象 件数
docs/.md 総数 354
うち superpowers/exclude_docs でサイト非公開) 255
公開 99

再現: git ls-tree -r --name-only f805c1b5 -- docs/ | grep -c '\.md$' → 354、 git ls-tree -r --name-only f805c1b5 -- docs/ | grep -c '^docs/superpowers/' → 255。 04287d13 では 353 / 255 / 98 で、増分 1 件は本評価書自身(#885)。

superpowers/(設計書・計画書・spec)を非公開にしているのは意図どおりだが、 「ドキュメントサイトを最新に保つ」という運用の対象は、書かれた文書の 3 割である。 残る 7 割はリポジトリを読める人だけが辿れる。

タブの偏り

タブは 9 本、nav 掲載 99 エントリ(ユニーク 94)。

タブ ページ数
ホーム 1
はじめに 8
機能仕様 9
操作手順書 16
JWNET 接続テスト 6
運用・技術 19
不変条件分冊 (AGENTS.md) 10
プロダクト管理 5
ADR 25

再現: git show f805c1b5:mkdocs.ymlnav を葉まで展開して数える。 エントリ 99・ユニーク 94。重複は 5 ページが各 2 回 (操作手順書 10 / 13 / 14 と runbook 2 本)で 99 - 5 = 94。 これは JWNET 接続テストタブの意図した横断キュレーション(#874)であり、問題ではない。

指名する 3 点(いずれも整理の候補であり、利用者の混乱の実測ではない)

  1. ADR 25 件が 1 タブに平坦。 内訳は索引 + ADR 0001〜0024。うち JWNET は 0019〜0024 の 6 件で、 最終の判断は 0024(採択)が 0023 を supersede したもの。鎖は 0019 → 0020 → 0021 → 0023 → 00240022 → 0023 → 0024 で、各本文に superseded / 保留 / 撤回と明記済みである。 置き換え関係が nav からは読めないのが整理の余地 (「矛盾する 6 決定を読者が目にする」かは測っていない仮説)。
  2. 運用・技術 19 件は目的別に並べ直せる。 Runbook 6 件は「Runbook:」の子グループに整理済み。 残る雑多さは認証 / テスト戦略 / デプロイ / 通知 2 件 / ハーネス 3 版 + テンプレート 2 件が 同じタブに並ぶ点で、読む目的が違うものが 1 タブに入っている。
  3. ハーネス設計の版が nav に 3 つ(v2・v6・v7)。v3/v4/v5 は参照経由(#881 で整理)。 3 版が並ぶ理由が nav から読めない(v7 は「現行」と明示済みだが v2・v6 の位置づけが不明)。

L2 アプリ導線

path チャネル 判断根拠 画面ファイル nav 到達 最短クリック数 到達経路
/collector 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorDashboardPage.tsx 可(サイドナビ直下) 0 入口 /collector
/collector/admin 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/profile
/collector/billing/invoices 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorBillingInvoicesPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「請求書」 → ハブ内タブ「確定済み」
/collector/billing/preview 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorBillingPreviewPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「請求書」
/collector/buyback-settlements 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorBuybackSettlementsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「買取精算」
/collector/dispatch 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES DispatchBoardPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「配車」
/collector/dispatch/courses 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CourseBuilderPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「配車」 → /collector/dispatch/courses(配車ボード内のリンク)
/collector/dispatch/templates 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/dispatch/courses
/collector/driver 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/driver
/collector/driver-daily-reports 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES DriverDailyReportsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「運行日報」
/collector/drivers 事務(admin限定) 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin DriverMasterPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「ドライバー」
/collector/drivers/:id 事務(admin限定) 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin DriverMasterDetailPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「ドライバー」 → /collector/drivers/:id(ドライバー一覧内のリンク)
/collector/external-partner-settlements 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ExternalPartnerSettlementsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「委託精算」
/collector/external-partners 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ExternalPartnersPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「委託先」
/collector/external-partners/:id 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorExternalPartnerDetailPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「委託先」 → /collector/external-partners/:id(委託先一覧内のリンク)
/collector/external-partners/import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ExternalPartnersImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「委託先」 → /collector/external-partners/import(委託先一覧内のリンク)
/collector/external-partners/item-rates-import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ExternalPartnerItemRatesImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「委託先」 → /collector/external-partners/item-rates-import(委託先一覧内のリンク)
/collector/external-partners/vehicle-leases-import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ExternalPartnerVehicleLeasesImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「委託先」 → /collector/external-partners/vehicle-leases-import(委託先一覧内のリンク)
/collector/fleet/costs 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES VehicleCostDashboardPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「コスト」
/collector/fleet/costs/import 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/fleet/costs/new
/collector/fleet/costs/new 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES VehicleCostNewPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「コスト」 → ハブ内タブ「取込」
/collector/fleet/costs/review 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CostOcrReviewPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「コスト」 → ハブ内タブ「レビュー」
/collector/fleet/daily-logs/new 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES VehicleDailyLogNewPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「コスト」 → /collector/fleet/daily-logs/new(コストハブ内のリンク)
/collector/fleet/factories 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/profile
/collector/fleet/vehicles 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES FleetVehiclesPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「車両」
/collector/fleet/vehicles/import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin FleetVehiclesImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「車両」 → /collector/fleet/vehicles/import(車両一覧内のリンク)
/collector/item-types 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ItemTypesPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「品目マスター」
/collector/item-unit-settings 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ItemUnitSettingsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「品目単位設定」
/collector/manifests 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorManifestPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「マニフェスト」
/collector/manifests/:id 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorManifestDetailPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「マニフェスト」 → /collector/manifests/:id(マニフェスト一覧内のリンク)
/collector/manifests/import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES BulkManifestImportPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「マニフェスト」 → ハブ内タブ「一括取込」
/collector/manifests/new 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorManifestCreatePage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「マニフェスト」 → /collector/manifests/new(マニフェスト一覧内のリンク)
/collector/manifests/review 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES ManifestReviewQueuePage.tsx 可(画面内リンク) 1 入口 /collector → /collector/manifests/review(概要ダッシュボード内のリンク)
/collector/members 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorMembersPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「メンバー」
/collector/partners 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorPartnersPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「取引先」
/collector/partners/:id 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorPartnerDetailPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「取引先」 → /collector/partners/:id(取引先一覧内のリンク)
/collector/partners/import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorPartnersImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「取引先」 → /collector/partners/import(取引先一覧内のリンク)
/collector/partners/sites-import 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorSitesImportPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「取引先」 → /collector/partners/sites-import(取引先一覧内のリンク)
/collector/profile 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorProfilePage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「会社情報」
/collector/reports/collection-volume 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectionVolumeReportPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「計量」 → ハブ内タブ「回収数量レポート」
/collector/secondary-cargo-types 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES SecondaryCargoTypesPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「二次搬出の運搬品目」
/collector/secondary-transport-routes 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES SecondaryTransportRoutesPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「二次搬出運搬費」
/collector/settings/billing 事務(admin限定) 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin CollectorBillingSettingsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「請求書発行設定」
/collector/settings/jwnet 事務(admin限定) 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin JwnetCredentialsSettingsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「JWNET設定」
/collector/settings/jwnet/connection-test 事務(admin限定) 接頭辞 /collector+allowedTenantType=collector・allowedRole=admin JwnetConnectionTestPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ「JWNET設定」 → /collector/settings/jwnet/connection-test(JWNET設定画面内のリンク)
/collector/walk-in-weighing 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES WalkInWeighingPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「計量」 → ハブ内タブ「持ち込み計量」
/collector/walk-in-weighing/list 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/weighings/list
/collector/weighings 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES CollectorWeighingPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ「計量」
/collector/weighings/import 事務 接頭辞 /collector+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/collector/weighings
/collector/weighings/list 事務 接頭辞 /collector+allowedTenantType=collector・allowedRole=COLLECTOR_STAFF_ROLES WeighingHistoryPage.tsx 可(ハブ内タブ) 2 入口 /collector → サイドナビ「計量」 → ハブ内タブ「計量一覧」
/driver ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable CollectorDriverHome.tsx 可(サイドナビ直下) 0 入口 /driver
/driver/board ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable DriverFactoryBoardPage.tsx 可(ハブ内タブ) 1 入口 /driver → タブ「工場の配車」(ホーム共通タブ)
/driver/daily-log ドライバーPWA 接頭辞 /driver+Navigate リダイレクト —(リダイレクト) 0(リダイレクト→/driver/daily-report
/driver/daily-report ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable DriverDailyReportPage.tsx 可(サイドナビ直下) 1 入口 /driver → サイドナビ「運行日報」(nav-config.tsx:222DRIVER_NAV
/driver/mekata-entered/:id ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable DriverMekataListPage.tsx 可(画面内リンク) 1 入口 /driver → /driver/mekata-entered/:id(ホームのCTA内のリンク)
/driver/profile ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable DriverProfileRegistrationPage.tsx 可(画面内リンク) 1 入口 /driver → /driver/profile(ドライバーホーム内のリンク)
/driver/run-weighing/:id ドライバーPWA 接頭辞 /driver+allowedTenantType=collector・allowDriverCapable DriverRunWeighingPage.tsx 可(画面内リンク) 1 入口 /driver → /driver/run-weighing/:id(ホームの運行データ確定時の遷移)
/discharger 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerDashboardPage.tsx 可(サイドナビ直下) 0 入口 /discharger
/discharger/collectors 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerCollectorListPage.tsx 可(サイドナビ直下) 1 入口 /discharger → サイドナビ「処理業者」
/discharger/collectors/:id 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerCollectorDetailPage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「処理業者」 → /discharger/collectors/:id(処理業者一覧内のリンク)
/discharger/invoices 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerInvoiceListPage.tsx 可(サイドナビ直下) 1 入口 /discharger → サイドナビ「請求書」
/discharger/invoices/:id 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerInvoiceDetailPage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「請求書」 → /discharger/invoices/:id(請求書一覧内のリンク)
/discharger/jobs 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerJobListPage.tsx 可(サイドナビ直下) 1 入口 /discharger → サイドナビ「案件」
/discharger/jobs/:id 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerJobDetailPage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「案件」 → /discharger/jobs/:id(案件一覧内のリンク)
/discharger/jobs/:id/match 排出事業者 接頭辞 /discharger+allowedTenantType=discharger ComingSoonPage.tsx 不可 到達不可(準備中・被リンク0)
/discharger/jobs/new 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerJobCreatePage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「案件」 → /discharger/jobs/new(案件一覧内のリンク)
/discharger/jwnet 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerJwnetPage.tsx 不可 到達不可(soon・意図的)
/discharger/manifests 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerManifestListPage.tsx 可(サイドナビ直下) 1 入口 /discharger → サイドナビ「マニフェスト」
/discharger/manifests/:id 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerManifestDetailPage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「マニフェスト」 → /discharger/manifests/:id(マニフェスト一覧内のリンク)
/discharger/manifests/form3 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerManifestForm3Page.tsx 可(ハブ内タブ) 2 入口 /discharger → サイドナビ「マニフェスト」 → ハブ内タブ「年度集計」
/discharger/manifests/import 排出事業者 接頭辞 /discharger+allowedTenantType=discharger BulkManifestImportPage.tsx 可(ハブ内タブ) 2 入口 /discharger → サイドナビ「マニフェスト」 → ハブ内タブ「一括取込」
/discharger/manifests/new 排出事業者 接頭辞 /discharger+allowedTenantType=discharger DischargerManifestCreatePage.tsx 可(画面内リンク) 2 入口 /discharger → サイドナビ「マニフェスト」 → /discharger/manifests/new(マニフェスト一覧内のリンク)
/discharger/manifests/review 排出事業者 接頭辞 /discharger+allowedTenantType=discharger ManifestReviewQueuePage.tsx 可(画面内リンク) 1 入口 /discharger → /discharger/manifests/review(概要ダッシュボード内のリンク)
/external-partner/settlements 委託先 接頭辞 /external-partner+allowedTenantType=external_partner ExternalPartnerSettlementsPage.tsx 可(サイドナビ直下) 0 入口 /external-partner/settlements
/settings 共通 接頭辞 /settings+ProtectedRouteのみ(要ログイン・ロール不問) SettingsPage.tsx 可(サイドナビ直下) 1 入口 /collector → サイドナビ secondary「設定」(nav-config.tsx:71 の直リンク)
/settings/billing 共通 接頭辞 /settings+allowedTenantTypes=[collector,discharger]・allowedRole=admin BillingSettingsPage.tsx 不可 到達不可(soon・意図的)
/settings/notifications 共通 接頭辞 /settings+ProtectedRouteのみ(要ログイン・ロール不問) NotificationsSettingsPage.tsx 不可 到達不可(被リンク0・指名)
/settings/security 共通 接頭辞 /settings+ProtectedRouteのみ(要ログイン・ロール不問) SecuritySettingsPage.tsx 可(画面内リンク) 2 入口 /collector → サイドナビ secondary「設定」 → /settings/security(設定タブ)
* 共通 ガードなし NotFoundPage.tsx 対象外(404) 不一致パスで表示
/ 共通 RoleBasedLanding(ロール別に振分) (RoleBasedLanding) —(入口振分) 対象外(入口振分) ロール別ホームへリダイレクト
/auth/callback 共通 ガードなし(公開) AuthCallback.tsx 不可 対象外(ログイン前・入口)
/register 共通 ガードなし(公開) RegisterOrgPage.tsx 不可 対象外(ログイン前・入口)
/signin 共通 ガードなし(公開) SignIn.tsx 不可 対象外(ログイン前・入口)
ルート 被リンク 判定
/discharger/jobs/:id/match(見積 AI マッチング) 0(ComingSoonPage 本体のみ) 意図的(準備中)。parentTo="/discharger/jobs" の戻りリンクはある
/discharger/jwnet(JWNET連携) 0(nav は status: 'soon' 意図的(未提供・ADR 0013 ゲート。ページ実体と準備中バナーは温存)
/settings/billing(課金・共通設定) SETTINGS_TABS のみ(status: 'soon' 意図的(機能未公開。直 URL の dev 検証は可能とコードに明記)
/settings/notifications(通知設定) 0 指名SettingsPage にサブ設定へのリンクは無く、SETTINGS_TABS にも含まれない(アカウント / セキュリティ / 課金のみ)。将来の分割予定コメントはあるが現状は URL 直打ちしかない

リダイレクト 8 件(/collector/admin/collector/driver/collector/weighings/import/collector/walk-in-weighing/list/collector/fleet/factories/collector/fleet/costs/import/collector/dispatch/templates/driver/daily-log)は旧 URL・旧パスの互換維持で意図的。 / はロール別振分、signinregisterauth/callback はログイン前の入口で数えない。

チャネルの区分けと外れた 1 件

事務 = /collector 配下の staff 画面(接頭辞+RoleGuard(collector)・admin 限定は下記の実測)、 ドライバーPWA = /driver 配下(allowDriverCapable)、排出事業者 = /discharger 配下、 共通 = ログイン・設定・公開入口で説明できた。外れたのは /external-partner/settlements の 1 件で、 委託先(衛星テナント・RoleGuard(external_partner))の独立チャネルとして数えた(4 区分に押し込めない)。

normal ロールとの差分

nav の差(3 件)とルートの差(7 件)は別物である。 混同すると normal が開ける範囲を誤って見積もる (nav から消える 3 件を admin 専用の全件と思うと、残る 4 件も normal が開けると誤解する)。

nav-config.tsxCOLLECTOR_ADMIN_ONLY_PATHSnav 項目の集合で 3 件 (/collector/drivers/collector/settings/jwnet/collector/settings/billing)。 一方 App.tsxallowedRole="admin" を持つルートnode scripts/app-ia-report.mjs --jsonroles を見ると 7 件ある。

admin 限定ルート nav 項目にもあるか
/collector/drivers あり
/collector/drivers/:id なし(一覧からの遷移)
/collector/settings/jwnet あり
/collector/settings/jwnet/connection-test なし(設定画面内)
/collector/settings/billing あり
/collector/fleet/vehicles/import なし(画面内リンク)
/settings/billing なし(共通チャネル・status: 'soon'

/collector 配下は 6 件、共通チャネルの /settings/billing を含めて 7 件normal は nav から消える 3 件だけでなく、この 7 件すべてに直接 URL でも到達できない (個別の RoleGuard が効く)。事務チャネルの本表の数値は admin ロールのものである。

L3 画面の質

DESIGN.md の主要規約のうち、指定した 3 点の静的適合性だけを機械的に数えた。 視認性・作業達成率・所要時間・エラーからの回復・キーボード操作・モバイル幅は測っていない。

規約 実測
高さは size prop に一元化(h-* 直書き禁止) プリミティブ(Select / SelectTrigger / Input / Button / Textarea)への直書き 0
入れ子カード禁止(Card の中の CardCardContent の中を含む) 0 件
tabular 単独トークン(tabular-nums への統一対象の既存ユーティリティ)の残存 0

再現: 高さ・入れ子は pnpm --filter @workspace/scripts exec vitest run ui-contract-ratchet.test.mjsapps/web/src 配下の .tsx を AST 走査。高さの検査対象は Input / Select / Button の 3 種、 入れ子の検査対象は Card の中の Card)。 表の残り(SelectTrigger / Textarea への直書き・CardContent の中の Cardtabular 単独・対象の非テスト .tsx 214 ファイル)は本評価時の独立 AST 走査で数えた。 JSX の className 文字列をトークン検査し import alias を解決する。 下記を l3-recount.mjs として保存し、リポジトリ直下で実行する (typescript はリポジトリの依存で解決する。保存したファイルはコミット不要・実行後に削除する):

node l3-recount.mjs
# → files=214 primitiveHeight=0 cardInCard=0 cardInCardContent=0 tabular=0
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import ts from 'typescript';

// 対象: 追跡中の apps/web/src 配下 .tsx から test/spec/stories を除く(L3 表の 214 件)
const prod = execFileSync('git', ['ls-files', 'apps/web/src'], { encoding: 'utf8' })
  .split('\n')
  .filter((p) => p.endsWith('.tsx') && !/\.(test|spec|stories)\./u.test(p));
const found = { files: prod.length, primitiveHeight: [], cardInCard: [], cardInCardContent: [], tabular: [] };
for (const path of prod) {
  const src = readFileSync(path, 'utf8');
  const ast = ts.createSourceFile(path, src, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
  const names = new Map();
  for (const s of ast.statements) {
    if (!ts.isImportDeclaration(s) || !s.importClause?.namedBindings
      || !ts.isNamedImports(s.importClause.namedBindings)) continue;
    for (const el of s.importClause.namedBindings.elements) {
      names.set(el.name.text, (el.propertyName ?? el.name).text);
    }
  }
  const tag = (node) => names.get(node.tagName.getText(ast)) ?? node.tagName.getText(ast);
  const texts = (node, acc = []) => {
    if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)
      || ts.isTemplateHead(node) || ts.isTemplateMiddle(node) || ts.isTemplateTail(node)) acc.push(node.text);
    else ts.forEachChild(node, (child) => texts(child, acc));
    return acc;
  };
  const at = (node, t, tokens) => {
    const line = ast.getLineAndCharacterOfPosition(node.getStart(ast)).line + 1;
    return { path, line, tag: t, tokens };
  };
  const visit = (node, cards = 0, contents = 0) => {
    let nextCards = cards;
    let nextContents = contents;
    if (ts.isJsxElement(node)) {
      const t = tag(node.openingElement);
      if (t === 'Card') {
        if (cards) found.cardInCard.push(at(node, t, []));
        if (contents) found.cardInCardContent.push(at(node, t, []));
        nextCards++;
      }
      if (t === 'CardContent') nextContents++;
    }
    if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
      const t = tag(node);
      const props = node.attributes.properties;
      const className = props.find((p) => p.name?.getText(ast) === 'className');
      const tokens = className?.initializer
        ? texts(className.initializer).flatMap((s) => s.split(/\s+/u))
        : [];
      const heights = tokens.filter((s) => /^(?:[^\s:]+:)*!?h-[^\s]+$/u.test(s));
      if (heights.length && ['Input', 'Select', 'SelectTrigger', 'Button', 'Textarea'].includes(t)) {
        found.primitiveHeight.push(at(node, t, heights));
      }
      if (tokens.includes('tabular')) found.tabular.push(at(node, t, ['tabular']));
      if (t === 'Card' && ts.isJsxSelfClosingElement(node)) {
        if (cards) found.cardInCard.push(at(node, t, []));
        if (contents) found.cardInCardContent.push(at(node, t, []));
      }
    }
    ts.forEachChild(node, (child) => visit(child, nextCards, nextContents));
  };
  visit(ast);
}
console.log(`files=${found.files} primitiveHeight=${found.primitiveHeight.length} `
  + `cardInCard=${found.cardInCard.length} cardInCardContent=${found.cardInCardContent.length} `
  + `tabular=${found.tabular.length}`);
if (found.primitiveHeight.length || found.cardInCard.length || found.cardInCardContent.length || found.tabular.length) {
  console.log(JSON.stringify(found, null, 2));
  process.exit(1);
}

tabular 単独は grep -rnE '(^|[^a-zA-Z0-9_-])tabular([^a-zA-Z0-9_-]|$)' apps/web/src --include='*.tsx' | grep -v '\.test\.' → 0 件。 tabular は無効クラスではなく apps/web/src/index.css:143 に定義済みの既存ユーティリティで、 DESIGN.md:72 も「定義済みだが新規は tabular-nums に揃える」と書いている。 .tsx 側の使用はすべて tabular-nums である。

本節は「指定した静的規約の検出違反は 0」とだけ読む。 「4 層のうち最も健全」や「改善の投資先ではない」は測った範囲を越えるため断定しない。 L2 / L3 の使いやすさ評価は未実施である。

L4 実装の複雑さ

測定 commit: f805c1b5(develop)。 母集団は apps/**packages/**.ts / .tsx で、テスト・story・生成 database.ts を除き、 LF 数が 500 を超えるファイル(>500。ちょうど 500 は対象外)。 services/jwnet-gateway/**tools/archify・node_modules・SQL・運用スクリプトは含めない。 「全体」と書くときはこの範囲を指す。

再現(62 / 57,721):

git ls-tree -r --name-only f805c1b5 -- apps packages \
  | grep -E '\.tsx?$' | grep -vE '\.(test|spec|stories)\.' \
  | grep -v '^packages/supabase/src/types/database.ts$' \
  | while read -r f; do printf '%s %s\n' "$(git show "f805c1b5:$f" | wc -l)" "$f"; done \
  | awk '$1>500{c++;s+=$1}END{print "files="c" lines="s}'
# → files=62 lines=57721

500 行超のファイル

区分 件数 行数
全体(apps + packages・上記条件) 62 57,721
うち JWNET(2026-10-18 まで凍結) 5 7,534
凍結外(いま触れる) 57 50,187

凍結 5 件の内訳(各 git show f805c1b5:<path> | wc -l で再現): apps/api/src/routes/jwnet.ts 3,046 / apps/web/src/lib/db/jwnet-web-edi.ts 538 / apps/web/src/pages/collector/jwnet/JwnetConnectionTestPage.tsx 958 / apps/web/src/pages/collector/jwnet/JwnetWebEdiPage.tsx 1,665 / packages/shared/src/jwnet/records/result-parser.ts 1,327(計 7,534)。

参考: services/jwnet-gateway/src/ にも同じ条件で 5 件 / 4,293 行があり、すべて凍結対象である (orchestrator.ts 1,646 / production.ts 783 / runtime.ts 502 / submissions/supabase-submissions-store.ts 528 / transport/biware-folder-transport.ts 834)。 サービスを含めると全体 67 件 / 62,014 行・凍結 10 件 / 11,827 行・凍結外 57 件 / 50,187 行になる。 本書の表は apps + packages に限った部分集合であり、凍結経路全体の規模ではない。

直接テストが無いもの — 候補の一覧(上限であり未テスト証明ではない)

初版は「テストが 1 本も無いもの」と書いたが、同名ファイルの有無だけでは既存検証を見落とす。 ここでは 静的契約テスト / 直接の挙動テスト / 親画面経由の挙動テスト を区別して再判定する。 「直接テストが無い」とは「そのファイル自身を直接 render・実行するテストが無い」の意味である。 全テスト経路の網羅は確認していないため、残る件数は候補の上限であって未テスト証明ではない

行数 ファイル 6 月以降の変更回数 既存検証(実ソースを読んで確認)
1,235 pages/collector/WalkInWeighingPage.tsx 11 未テスト候補
984 App.tsx 83 静的契約テスト 11 本(App.route-gates.test.ts)。tenant/role guard・driver・JWNET・manifest のルート契約を assert。動的な全画面遷移テストは無い
981 dispatch-board/use-dispatch-board-data.ts 2 #880 で直接の挙動テストが付いた(同名テスト有り)。本節の候補からは外れる
852 fleet/cost-dashboard/cost-entry-table.tsx 5 親画面経由の挙動テスト(VehicleCostDashboardPage.render.test.tsx の保存済みコスト明細節でページ合計・展開・編集を検証。実コンポーネントを render)
838 pages/collector/CollectorProfilePage.tsx 17 未テスト候補
725 dispatch/CourseBuilderPage.tsx 16 未テスト候補
718 pages/driver-home/use-driver-route-data.ts 4 親画面経由の挙動テスト 12 本(use-driver-route-data.freshness.test.tsx。実 CollectorDriverHome が実 hook を呼び、再取得・横乗り情報の失効時表示を検証。hook 自体はモックしていない)
682 pages/collector/CollectorPartnerDetailPage.tsx 30 未テスト候補
637 fleet/cost-dashboard/summary-tab.tsx 4 親画面経由の挙動テスト(同 render テストの費目別内訳節で集計を検証。実コンポーネントを render)
597 hooks/useAdScaleSerial.ts 10 未テスト候補(関連する純関数テストと hook の検証は区別が必要)
520 hooks/useTruckScaleFile.ts 1 未テスト候補(同上)
507 fleet/VehicleDailyLogNewPage.tsx 17 未テスト候補

初版の 12 件 / 9,276 行から既存検証のある 4 件(App.tsx 984 + cost-entry-table.tsx 852 + summary-tab.tsx 637 + use-driver-route-data.ts 718 = 3,191 行)を除くと 8 件 / 6,085 行。 さらに #880 のマージで配車フック(981 行)に直接テストが付き、7 件 / 5,104 行以下になる。

変更回数の条件は git log --since=2026-06-01T00:00:00+09:00 --format=%H f805c1b5 -- <path> | wc -l (JST の 6/1 00:00 を起点・merge を含む。日付だけの --since=2026-06-01 では 同日 09:26:45 の 0369e07d が境界外になり App.tsx は 82 回になる)。 同条件で DispatchBoardPage.tsx(361 行・500 行超の集合外)は 92 回のため、 App.tsx の 83 回は「500 行超のファイルの中で最多」に限定する(「全ファイル中最多」ではない)。 App.tsx<Route 文字列一致 84 は <Routes> を 1 個含む。<Route> 要素は 83、 lazy( は 69 である(git show f805c1b5:apps/web/src/App.tsx に対し grep -c '<Route' → 84、grep -oE '<Route($|[^a-zA-Z])' | wc -l → 83、grep -c 'lazy(' → 69)。 ルーティングの個数と文字列一致数を区別する。

指名する 2 点

  1. App.tsx は変更頻度と規模の組み合わせが大きい — 984 行・6 月以降 83 回変更 (500 行超のファイルの中で最多。上記条件)・<Route> 要素 83 個のルーティング表そのもの。 既存の静的契約テスト 11 本(App.route-gates.test.ts)が tenant/role guard 等を守っているため、 追加投資は「既存 11 本に不足する契約」(動的な全画面遷移など)を示して判断する。 L2 の実測(ルート 83 本・チャネル・到達可否)がそのまま期待値になるので、 測定結果を検査に転化できる
  2. use-dispatch-board-data.ts(981 行)は #880 で characterization test を先に置いた。 この「大きい・直接テスト無し → まず挙動を固定 → 次に分割」という型を、 残る候補へ順に当てるのが L4 の進め方になる。

死蔵コード 1 件

L2 の実測で、ルートも本番ソースからの被 import も持たないページファイルは JwnetWebEdiPage.tsx(1,665 行)の 1 件のみ。 ADR 0024 で Web-EDI は本番運用しないと決まっている。JWNET 凍結の対象なので 10/18 までは触らない。 撤去は ADR 0024 の条件(標準EDI の接続テスト通過=実運用開始後に撤去 PR)に従い、 10/19 になったことだけでは出さない。なお render テストから import されているため、 物理削除には関連テストの処置も必要である(単純なファイル削除では済まない)。

4 層を通した結論

判定 決め手
L1 docs 情報設計 要改善の候補 書かれた 354 件のうち公開は 99 件f805c1b5)。ADR 25 件が平坦で置換関係が nav から読めない
L2 アプリ導線 静的には浅い 到達可 66 本すべて 2 クリック以内(デスクトップ)。指名は /settings/notifications の 1 件。使いやすさは未測定
L3 画面の質 指定した静的規約の検出違反は 0 DESIGN.md の主要規約のうち指定 3 点を測定。使いやすさ全般は未評価
L4 実装の複雑さ 要改善の候補 500 行超 62 件 / 57,721 行(apps + packages)。直接テスト無しは候補 7 件 / 5,104 行以下

重点の候補は L4 と L1 に置く。 L2 はデスクトップの最短到達クリック数に大きな深さが見られず、 L3 は指定した静的規約の検出違反が 0 だったが、使いやすさは未測定のため投資不要は断定しない。

着手の順序(根拠つき)

  1. App.tsx の不足契約を示して判断する。 984 行・6 月以降 83 回変更 (500 行超のファイルの中で最多。上記条件)<Route> 要素 83 個のルーティング表そのもの。 既存の静的契約テスト 11 本に不足する契約(動的な全画面遷移など)があれば足す。 L2 の実測(ルート 83 本・チャネル・到達可否)がそのまま期待値になるので、測定結果を検査に転化できる
  2. 残る「大きい・直接テスト無し」候補へ #880 の型を当てる。 characterization test で挙動を固定 → 分割、の順。分割を先にしない。
  3. L1 は ADR タブの整理から。 0024 が 0023 を supersede した置換関係を nav で読めるようにする。 当日の操作者が矛盾する決定を並べて目にするおそれを減らすのが先で、 運用・技術タブ 19 件の再編はその後(いずれも利用者の混乱の実測ではなく仮説)。
  4. JwnetWebEdiPage.tsx(1,665 行)の撤去は標準EDI の接続テスト通過後に PR を出す。 ルートも本番ソースからの被 import も無く、ADR 0024 で本番運用しないと決まっているが、 撤去条件は日付(10/19)ではなく接続テスト通過であり、render テストの処置も必要である。

この文書の測定で気づいたこと

数え方を絞らずに出した数字は誤った結論を導く。 実例:

  • h-* の直書きは、絞らずに数えるとアイコン・スピナー・レイアウト用まで混ざる。 size prop を持つプリミティブへの直書きに絞ると 0 であり、絞らなければ不要なリファクタを指示していた。 (初版の「211 箇所」は対象・コマンドが無く再現できないため落とした)
  • admin 限定の件数は、nav 項目の集合(3)とルートの集合(7)を混同して 3 箇所で違う値を書いていた。
  • 「3 クリック以上は 0」は viewport を定めずに書くと再現できない(モバイルはサイドナビが出ない)。

数え方の定義と基準 commit を本文に置くことを、この種の文書の規約とする。