システム品質評価(2026-09)¶
禹さん指示「システムの品質を全体的に高めたい。UI/UX と情報設計を見直したい」の第一歩として、 現状を数字で出す。所感ではなく実測を置く。
測定条件¶
- 測定日: 2026-09-19
- 測定対象:
04287d13(develop) - L1(docs 情報設計)は初版で
04287d13時点の集計と #881 後の記述が混在していたため、f805c1b5(develop)に統一して取り直した。本節 L1 の数値はすべてこの commit 時点である。 L2 のルート・導線の数値と L4 の数値は04287d13とf805c1b5で同一であることを確認済み (ルート 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 (04287d13をf805c1b5に替えると 230。node scripts/app-ia-report.mjs --jsonの head 出力もrouteFiles72・imported61・testFiles96・unimported1=計 230 件・非ルート 158 件) - 機械部分の再実行:
node scripts/app-ia-report.mjs --json(ルート一覧・nav 到達可否の素・被 import 0 ページ。クリック数とチャネル判断は本書の表)
数え方の定義¶
最短クリック数はチャネル入口(ログイン直後の着地画面。実コードの resolveLandingPath を読んで決めた。
事務 /collector・ドライバー /driver・排出事業者 /discharger・委託先 /external-partner/settlements)
からの最小クリックである。
測定はデスクトップ幅(md 以上・サイドナビが可視)に限る。
SideNav.tsx:43 は hidden … 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.yml の nav を葉まで展開して数える。
エントリ 99・ユニーク 94。重複は 5 ページが各 2 回
(操作手順書 10 / 13 / 14 と runbook 2 本)で 99 - 5 = 94。
これは JWNET 接続テストタブの意図した横断キュレーション(#874)であり、問題ではない。
指名する 3 点(いずれも整理の候補であり、利用者の混乱の実測ではない)¶
- ADR 25 件が 1 タブに平坦。 内訳は索引 + ADR 0001〜0024。うち JWNET は 0019〜0024 の 6 件で、
最終の判断は 0024(採択)が 0023 を supersede したもの。鎖は
0019 → 0020 → 0021 → 0023 → 0024と0022 → 0023 → 0024で、各本文に superseded / 保留 / 撤回と明記済みである。 置き換え関係が nav からは読めないのが整理の余地 (「矛盾する 6 決定を読者が目にする」かは測っていない仮説)。 - 運用・技術 19 件は目的別に並べ直せる。 Runbook 6 件は「Runbook:」の子グループに整理済み。 残る雑多さは認証 / テスト戦略 / デプロイ / 通知 2 件 / ハーネス 3 版 + テンプレート 2 件が 同じタブに並ぶ点で、読む目的が違うものが 1 タブに入っている。
- ハーネス設計の版が 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:222 の DRIVER_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 |
不可 | 対象外(ログイン前・入口) | — |
nav から辿れない 4 件の判定¶
| ルート | 被リンク | 判定 |
|---|---|---|
/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・旧パスの互換維持で意図的。
/ はロール別振分、signin・register・auth/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.tsx の COLLECTOR_ADMIN_ONLY_PATHS は nav 項目の集合で 3 件
(/collector/drivers・/collector/settings/jwnet・/collector/settings/billing)。
一方 App.tsx で allowedRole="admin" を持つルートは
node scripts/app-ia-report.mjs --json の roles を見ると 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 の中の Card・CardContent の中を含む) |
0 件 |
tabular 単独トークン(tabular-nums への統一対象の既存ユーティリティ)の残存 |
0 |
再現: 高さ・入れ子は pnpm --filter @workspace/scripts exec vitest run ui-contract-ratchet.test.mjs
(apps/web/src 配下の .tsx を AST 走査。高さの検査対象は Input / Select / Button の 3 種、
入れ子の検査対象は Card の中の Card)。
表の残り(SelectTrigger / Textarea への直書き・CardContent の中の Card・
tabular 単独・対象の非テスト .tsx 214 ファイル)は本評価時の独立 AST 走査で数えた。
JSX の className 文字列をトークン検査し import alias を解決する。
下記を l3-recount.mjs として保存し、リポジトリ直下で実行する
(typescript はリポジトリの依存で解決する。保存したファイルはコミット不要・実行後に削除する):
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 点¶
App.tsxは変更頻度と規模の組み合わせが大きい — 984 行・6 月以降 83 回変更 (500 行超のファイルの中で最多。上記条件)・<Route>要素 83 個のルーティング表そのもの。 既存の静的契約テスト 11 本(App.route-gates.test.ts)が tenant/role guard 等を守っているため、 追加投資は「既存 11 本に不足する契約」(動的な全画面遷移など)を示して判断する。 L2 の実測(ルート 83 本・チャネル・到達可否)がそのまま期待値になるので、 測定結果を検査に転化できる。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 だったが、使いやすさは未測定のため投資不要は断定しない。
着手の順序(根拠つき)¶
App.tsxの不足契約を示して判断する。 984 行・6 月以降 83 回変更 (500 行超のファイルの中で最多。上記条件)・<Route>要素 83 個のルーティング表そのもの。 既存の静的契約テスト 11 本に不足する契約(動的な全画面遷移など)があれば足す。 L2 の実測(ルート 83 本・チャネル・到達可否)がそのまま期待値になるので、測定結果を検査に転化できる。- 残る「大きい・直接テスト無し」候補へ #880 の型を当てる。 characterization test で挙動を固定 → 分割、の順。分割を先にしない。
- L1 は ADR タブの整理から。 0024 が 0023 を supersede した置換関係を nav で読めるようにする。 当日の操作者が矛盾する決定を並べて目にするおそれを減らすのが先で、 運用・技術タブ 19 件の再編はその後(いずれも利用者の混乱の実測ではなく仮説)。
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 を本文に置くことを、この種の文書の規約とする。