<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Onboarding on Tarragon</title><link>https://tarrragon.github.io/blog/tags/onboarding/</link><description>Recent content in Onboarding on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 30 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/onboarding/index.xml" rel="self" type="application/rss+xml"/><item><title>拿到雲端帳號的第一天</title><link>https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/</guid><description>&lt;p>這篇寫給一種特定的讀者：你的專業可能是後端、前端、資料工程或其他領域，但因為組織需要，你被指派處理雲端基礎設施。公司（或主管）給了你一個 AWS / GCP / Azure 帳號，你登入之後看到一個很大的 Console，不確定該做什麼、也不確定動了什麼會出事。&lt;/p>
&lt;p>這是 infra 工作最常見的真實入口。比起從零自學建一套環境，「接到指派、拿到帳號、搞清楚狀況」才是多數工程師第一次碰 infra 的方式。&lt;/p>
&lt;p>這篇用 AWS 為主要範例。GCP 和 Azure 的判讀邏輯相同（安全底線 → 現況盤點 → 路線分流），但具體服務名稱、IAM 模型和 Console 操作位置不同。&lt;/p>
&lt;h2 id="第一小時安全底線">第一小時：安全底線&lt;/h2>
&lt;p>登入帳號後，在做任何其他事之前先完成這些。這些步驟的共同目的是確保帳號的存取控制處於安全狀態——雲端帳號被入侵的代價遠高於本機電腦被入侵，因為雲端資源可以在幾分鐘內被大量建立（產生帳單）或被刪除（資料遺失）。&lt;/p>
&lt;h3 id="確認-root-帳號的-mfa">確認 root 帳號的 MFA&lt;/h3>
&lt;p>Root 帳號是雲端環境的最高權限，能做任何事，包括關閉整個帳號。如果 root 帳號沒有 MFA（Multi-Factor Authentication，多因子驗證），任何拿到 root 密碼的人都能完全控制整個環境。&lt;/p>
&lt;p>確認路徑（AWS）：Console 右上角帳號名稱 → Security credentials → Multi-factor authentication (MFA)。如果顯示「No MFA device」，立刻設定一個——手機 app（Google Authenticator / Authy）或硬體 key（YubiKey）都可以。&lt;/p>
&lt;p>如果你拿到的帳號是公司用 AWS Organizations 開出來的子帳號，子帳號 root 的密碼和 MFA 是獨立的——管理帳號無法代設。子帳號 root 通常需要先用帳號 email 做密碼重置才能首次登入。確認 root MFA 後，日常操作用 IAM Identity Center 登入。&lt;/p>
&lt;h3 id="確認你的登入身分">確認你的登入身分&lt;/h3>
&lt;p>你登入用的是哪種身分？這決定了你的權限範圍和操作方式。&lt;/p>
&lt;p>&lt;strong>IAM user&lt;/strong>：Console 右上角會顯示 &lt;code>username @ account-id&lt;/code>。這是最傳統的登入方式——帳號管理員幫你建了一個使用者，給了你一組帳密。&lt;/p>
&lt;p>&lt;strong>IAM Identity Center（SSO）&lt;/strong>：你透過一個特別的登入頁面（通常是 &lt;code>https://d-xxxxxxxxxx.awsapps.com/start&lt;/code>）登入，然後選擇帳號和角色。這是較新的做法，多帳號組織常用。&lt;/p>
&lt;p>&lt;strong>Root 帳號&lt;/strong>：Console 右上角顯示帳號 email 而非 username。如果你拿到的是 root 帳號的帳密，日常操作應該換成 IAM user 或 SSO 登入——root 帳號只在需要 root-only 操作（如設定 MFA、關閉帳號）時使用。建立 IAM user 的方式見模組一的&lt;a href="https://tarrragon.github.io/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提&lt;/a>段。&lt;/p>
&lt;h3 id="檢查既存的-access-key">檢查既存的 access key&lt;/h3>
&lt;p>帳號如果被前人用過，可能有暴露風險的 access key——之前的管理員建了 IAM user、生了 key，但那組 key 可能已經寫在某個 Git repo 或環境變數裡而沒有停用。&lt;/p>
&lt;p>確認路徑：Console → IAM → Users → 逐一點每個 user → Security credentials 分頁 → Access keys。檢查每組 key 的狀態（Active / Inactive）和建立時間。超過 90 天未 rotate 的 Active key 是風險——帳號接手後優先 rotate 或停用這些 key。如果帳號裡沒有任何 IAM user，這步跳過。&lt;/p>
&lt;h3 id="確認-cloudtrail-是否開啟">確認 CloudTrail 是否開啟&lt;/h3>
&lt;p>CloudTrail 記錄帳號內所有 API 操作（誰在什麼時間做了什麼）。AWS 預設會開啟 90 天的事件歷史，但長期保存需要建一個 Trail 把 log 寫到 S3。&lt;/p>
&lt;p>確認路徑：Console 搜尋 CloudTrail → Dashboard。如果有 Trail 已建立，表示操作紀錄有長期保存。如果只有預設的 Event history，90 天前的紀錄會消失——這是一個需要但不緊急的改善點，&lt;a href="https://tarrragon.github.io/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性&lt;/a>會展開。&lt;/p></description><content:encoded><![CDATA[<p>這篇寫給一種特定的讀者：你的專業可能是後端、前端、資料工程或其他領域，但因為組織需要，你被指派處理雲端基礎設施。公司（或主管）給了你一個 AWS / GCP / Azure 帳號，你登入之後看到一個很大的 Console，不確定該做什麼、也不確定動了什麼會出事。</p>
<p>這是 infra 工作最常見的真實入口。比起從零自學建一套環境，「接到指派、拿到帳號、搞清楚狀況」才是多數工程師第一次碰 infra 的方式。</p>
<p>這篇用 AWS 為主要範例。GCP 和 Azure 的判讀邏輯相同（安全底線 → 現況盤點 → 路線分流），但具體服務名稱、IAM 模型和 Console 操作位置不同。</p>
<h2 id="第一小時安全底線">第一小時：安全底線</h2>
<p>登入帳號後，在做任何其他事之前先完成這些。這些步驟的共同目的是確保帳號的存取控制處於安全狀態——雲端帳號被入侵的代價遠高於本機電腦被入侵，因為雲端資源可以在幾分鐘內被大量建立（產生帳單）或被刪除（資料遺失）。</p>
<h3 id="確認-root-帳號的-mfa">確認 root 帳號的 MFA</h3>
<p>Root 帳號是雲端環境的最高權限，能做任何事，包括關閉整個帳號。如果 root 帳號沒有 MFA（Multi-Factor Authentication，多因子驗證），任何拿到 root 密碼的人都能完全控制整個環境。</p>
<p>確認路徑（AWS）：Console 右上角帳號名稱 → Security credentials → Multi-factor authentication (MFA)。如果顯示「No MFA device」，立刻設定一個——手機 app（Google Authenticator / Authy）或硬體 key（YubiKey）都可以。</p>
<p>如果你拿到的帳號是公司用 AWS Organizations 開出來的子帳號，子帳號 root 的密碼和 MFA 是獨立的——管理帳號無法代設。子帳號 root 通常需要先用帳號 email 做密碼重置才能首次登入。確認 root MFA 後，日常操作用 IAM Identity Center 登入。</p>
<h3 id="確認你的登入身分">確認你的登入身分</h3>
<p>你登入用的是哪種身分？這決定了你的權限範圍和操作方式。</p>
<p><strong>IAM user</strong>：Console 右上角會顯示 <code>username @ account-id</code>。這是最傳統的登入方式——帳號管理員幫你建了一個使用者，給了你一組帳密。</p>
<p><strong>IAM Identity Center（SSO）</strong>：你透過一個特別的登入頁面（通常是 <code>https://d-xxxxxxxxxx.awsapps.com/start</code>）登入，然後選擇帳號和角色。這是較新的做法，多帳號組織常用。</p>
<p><strong>Root 帳號</strong>：Console 右上角顯示帳號 email 而非 username。如果你拿到的是 root 帳號的帳密，日常操作應該換成 IAM user 或 SSO 登入——root 帳號只在需要 root-only 操作（如設定 MFA、關閉帳號）時使用。建立 IAM user 的方式見模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提</a>段。</p>
<h3 id="檢查既存的-access-key">檢查既存的 access key</h3>
<p>帳號如果被前人用過，可能有暴露風險的 access key——之前的管理員建了 IAM user、生了 key，但那組 key 可能已經寫在某個 Git repo 或環境變數裡而沒有停用。</p>
<p>確認路徑：Console → IAM → Users → 逐一點每個 user → Security credentials 分頁 → Access keys。檢查每組 key 的狀態（Active / Inactive）和建立時間。超過 90 天未 rotate 的 Active key 是風險——帳號接手後優先 rotate 或停用這些 key。如果帳號裡沒有任何 IAM user，這步跳過。</p>
<h3 id="確認-cloudtrail-是否開啟">確認 CloudTrail 是否開啟</h3>
<p>CloudTrail 記錄帳號內所有 API 操作（誰在什麼時間做了什麼）。AWS 預設會開啟 90 天的事件歷史，但長期保存需要建一個 Trail 把 log 寫到 S3。</p>
<p>確認路徑：Console 搜尋 CloudTrail → Dashboard。如果有 Trail 已建立，表示操作紀錄有長期保存。如果只有預設的 Event history，90 天前的紀錄會消失——這是一個需要但不緊急的改善點，<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性</a>會展開。</p>
<p>現階段只需要確認 CloudTrail 存在，不需要馬上改它。</p>
<h3 id="設定帳單警報">設定帳單警報</h3>
<p>雲端帳單是開放式的——資源跑著就持續產生費用，被入侵後被開出大量資源更可能在幾小時內累積數千美元帳單。設一個帳單警報，超過閾值時收到通知。</p>
<p>設定路徑（AWS）：Console 搜尋 Billing → Budgets → Create budget → Cost budget。設一個月預算（如 $50 或 $100，依你的環境規模），超過 80% 和 100% 時發 email 通知。</p>
<h2 id="帳號現況判讀空帳號還是有東西">帳號現況判讀：空帳號還是有東西？</h2>
<p>安全底線做完後，下一步是搞清楚帳號的現況。這決定了你接下來走哪條路線。</p>
<h3 id="怎麼判斷">怎麼判斷</h3>
<p>EC2 Dashboard 只顯示當前 region 的資源。Console 右上角有 region 選擇器——先切幾個主要 region（us-east-1、ap-northeast-1、ap-southeast-1）看一下，確認資源是否分散在不同 region。</p>
<p>打開 EC2 Dashboard（Console 搜尋 EC2）。如果 Running instances 是 0、沒有 volumes、沒有 security groups（除了 default）——大概率是空帳號。也檢查 Lambda（Console 搜尋 Lambda → Functions）——如果有 function 在跑但 EC2 是空的，可能是 serverless 架構，帳號不是空的。</p>
<p>再看 S3（Console 搜尋 S3）。S3 是全域服務，不分 region。如果沒有 bucket，或只有 CloudTrail 的 log bucket——大概率是空帳號。</p>
<p>如果有正在跑的 EC2 instance、有 Lambda function、有 RDS 資料庫、有 S3 bucket 存著資料——這是一個有東西的帳號，可能是前人建的、可能是其他團隊在用的。</p>
<h3 id="空帳號--從零建置">空帳號 → 從零建置</h3>
<p>帳號是空的，你要從零開始建基礎設施。這是最乾淨的起點。</p>
<p>路線：先讀<a href="/blog/infra/00-infra-mindset/" data-link-title="模組零：infra 是什麼，為什麼 day 1 就要鋪地基" data-link-desc="基礎設施的責任邊界、成熟度階梯，以及地基為什麼總在環境爆炸時才被看見">模組零</a>建立心智模型（什麼是 infra、成熟度階梯），然後照模組一到五的順序走。模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">動手前的前提</a>段會帶你設好本機工具和認證。</p>
<h3 id="有東西的帳號--接手維運">有東西的帳號 → 接手維運</h3>
<p>帳號裡已經有資源在跑。你需要先搞清楚「有什麼」「誰建的」「哪些還在用」，再決定怎麼處理。</p>
<p>路線：讀<a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>模組。它按環境類型（全手動的遺留環境、部分有 IaC、多帳號結構）分篇，教你怎麼盤點、怎麼在不搞壞的前提下逐步接管。</p>
<h3 id="不確定--先盤點再說">不確定 → 先盤點再說</h3>
<p>如果帳號裡有東西但你不確定是不是還在用、能不能動，先盤點。以下指令需要 AWS CLI 並完成認證——安裝和 <code>aws configure</code> 設定見模組一的<a href="/blog/infra/01-minimal-iac/iac-tool-state-backend/" data-link-title="IaC 工具選型與 state 地基" data-link-desc="Terraform / OpenTofu / CDK / Pulumi 的選型判準，state 作為 IaC 工具對現實的唯一記憶，以及 remote state backend 的自管與託管路線">前提段</a>（macOS 快速安裝：<code>brew install awscli &amp;&amp; aws configure</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 列出所有 region 的 EC2 instance</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">for</span> region in <span class="k">$(</span>aws ec2 describe-regions --query <span class="s1">&#39;Regions[].RegionName&#39;</span> --output text<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="nv">$region</span><span class="s2"> ===&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  aws ec2 describe-instances --region <span class="s2">&#34;</span><span class="nv">$region</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="se"></span>    --query <span class="s1">&#39;Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="se"></span>    --output table
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 列出所有 S3 bucket</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">aws s3 ls
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 列出所有 RDS instance</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">aws rds describe-db-instances <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>  --query <span class="s1">&#39;DBInstances[].[DBInstanceIdentifier,Engine,DBInstanceStatus]&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="se"></span>  --output table</span></span></code></pre></div><p>這些指令只做讀取，不會改變任何東西。如果輸出很多資源，去讀<a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a>再決定下一步。如果幾乎是空的，走「從零建置」路線。</p>
<h2 id="雲端-console-的基本導覽">雲端 Console 的基本導覽</h2>
<p>AWS Console 列出幾百個服務，日常 infra 工作常用的集中在以下幾個：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>做什麼</th>
          <th>什麼時候用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>EC2</td>
          <td>虛擬機器（運算）</td>
          <td>看有什麼機器在跑、管 security group</td>
      </tr>
      <tr>
          <td>S3</td>
          <td>物件儲存</td>
          <td>放檔案、放 Terraform state、放 log</td>
      </tr>
      <tr>
          <td>IAM</td>
          <td>身分與權限</td>
          <td>管使用者、角色、權限</td>
      </tr>
      <tr>
          <td>VPC</td>
          <td>虛擬網路</td>
          <td>管網路拓撲、子網路、路由</td>
      </tr>
      <tr>
          <td>RDS</td>
          <td>託管資料庫</td>
          <td>看有沒有資料庫在跑</td>
      </tr>
      <tr>
          <td>CloudWatch</td>
          <td>監控與 log</td>
          <td>看 metric、設 alarm、查 log</td>
      </tr>
      <tr>
          <td>CloudTrail</td>
          <td>操作審計</td>
          <td>查誰做了什麼</td>
      </tr>
      <tr>
          <td>Billing</td>
          <td>帳單</td>
          <td>看花了多少錢</td>
      </tr>
  </tbody>
</table>
<p>Console 左上角的搜尋列可以直接搜服務名稱，不用從選單找。</p>
<p>每個服務在 Console 上的操作都有一個對應的 AWS CLI 指令和 API 呼叫。這個對應關係是 IaC 的基礎——<a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一</a>會教怎麼把 Console 上的操作轉成程式碼。</p>
<h2 id="你接下來該讀什麼">你接下來該讀什麼</h2>
<p>根據你的情境選一條路線：</p>
<table>
  <thead>
      <tr>
          <th>你的情境</th>
          <th>路線</th>
          <th>從哪裡開始</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>完全沒碰過雲端、想先理解概念</td>
          <td>入門認識</td>
          <td><a href="/blog/infra/00-infra-mindset/personal-project-to-infra/" data-link-title="雲端部署裡已經存在的 infra 元件" data-link-desc="VPC、security group、IAM、儲存 — 這些元件在任何雲端部署裡都已經在運作，差別在於有沒有被有意識地管理">個人專案到團隊服務</a></td>
      </tr>
      <tr>
          <td>空帳號、要從零建 infra</td>
          <td>從零建置</td>
          <td><a href="/blog/infra/01-minimal-iac/" data-link-title="模組一：最小可行 IaC — state 地基與 Console 唯讀鐵律" data-link-desc="Terraform / OpenTofu 選型、remote state 與 lock，以及「Console 只能看不能改」鐵律">模組一：最小可行 IaC</a></td>
      </tr>
      <tr>
          <td>帳號有東西、要接手維運</td>
          <td>接手前人專案</td>
          <td><a href="/blog/infra/takeover/" data-link-title="接手維運：別人建的環境怎麼接管" data-link-desc="接手前人的專案時，怎麼在不搞壞東西的前提下盤點現況、建立維運能力、逐步正規化 — 從無 SSH 的 FTP 環境到有半套 IaC 的雲端環境都適用">接手維運</a></td>
      </tr>
      <tr>
          <td>手動環境、暫時無法導入 IaC</td>
          <td>還沒有 IaC</td>
          <td><a href="/blog/infra/before-infra/" data-link-title="模組負一：還沒有 infra 的環境怎麼盡量做好" data-link-desc="手動點起家的環境怎麼守底線、降低未來納管成本、辨識何時該開始導入 IaC — 給還沒有能力上 IaC 的真實起點">模組負一：還沒有 infra 的環境</a></td>
      </tr>
      <tr>
          <td>要跟主管解釋為什麼要做 infra</td>
          <td>說服決策者</td>
          <td><a href="/blog/infra/09-driving-adoption/infra-explained-for-non-engineers/" data-link-title="給非工程背景決策者的 infra 說明" data-link-desc="從管理視角解釋基礎設施在解決什麼營運問題、不做的代價、出事怎麼處理，讓參與資源決策的人能判斷投入的優先級">給非工程人員的 infra 說明</a></td>
      </tr>
      <tr>
          <td>拿到一台主機、要從 OS 層連入初始化</td>
          <td>機器初始化</td>
          <td><a href="/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化</a></td>
      </tr>
  </tbody>
</table>
<p>如果你不確定自己屬於哪種情境，先做完本篇的「帳號現況判讀」再決定。</p>
]]></content:encoded></item><item><title>U.C4 首頁缺配對入口按鈕、導航流未完整列出</title><link>https://tarrragon.github.io/blog/ux-design/cases/missing-enrollment-entry-point/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/missing-enrollment-entry-point/</guid><description>&lt;p>這個案例的核心責任是說明導航流設計必須覆蓋所有操作情境的入口，不只是最常用的那個。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 首頁在 W2-001 修復前只有一個按鈕：Connect Terminal（對應 UC-02 日常連線）。配對功能（UC-01 首次配對）沒有入口 — &lt;code>EnrollmentScreen&lt;/code> 和 &lt;code>QrScannerScreen&lt;/code> 都存在且可運作，但首頁沒有按鈕導航過去。&lt;/p>
&lt;p>Router 定義了三條路由，全部可存取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">GoRouter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">routes:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">HomeScreen&lt;/span>&lt;span class="p">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/enrollment&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">EnrollmentScreen&lt;/span>&lt;span class="p">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="n">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/terminal&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">TerminalScreen&lt;/span>&lt;span class="p">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">]);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但 HomeScreen 只有一個 &lt;code>context.go('/terminal')&lt;/code> 按鈕，&lt;code>/enrollment&lt;/code> 路由存在但從 UI 無法到達。&lt;/p>
&lt;p>W2-001 修復加入 &lt;code>OutlinedButton.icon&lt;/code> 連結到 &lt;code>/enrollment&lt;/code>，並用 &lt;code>context.push&lt;/code>（非 &lt;code>context.go&lt;/code>）讓配對完成後能返回首頁。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>影響&lt;/td>
 &lt;td>首次使用者無法配對（功能存在但入口缺失）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復&lt;/td>
 &lt;td>加一個 &lt;code>OutlinedButton&lt;/code> + &lt;code>context.push('/enrollment')&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>根因&lt;/td>
 &lt;td>導航流只設計了「日常連線」入口，遺漏「首次配對」入口&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>操作盤點有四個操作，首頁只有一個入口&lt;/strong>。操作盤點段列出四個操作：配對、連線、輪替、啟停。首頁應該是這四個操作的導航 hub，至少要有「配對」和「連線」兩個入口（輪替和啟停是主機端操作，不需要 app 入口）。只放 Connect Terminal 等於假設「使用者已經配對過」。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>路由存在但 UI 不可達 = 死程式碼的 UX 版本&lt;/strong>。&lt;code>/enrollment&lt;/code> 路由在 router 裡定義了，&lt;code>EnrollmentScreen&lt;/code> 也完整實作了，但使用者從 UI 無法觸及。這跟寫了函式但沒有呼叫者一樣 — 功能正確但不可存取。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>go&lt;/code> vs &lt;code>push&lt;/code> 的語意差異影響 UX&lt;/strong>。W2 修復用 &lt;code>context.push('/enrollment')&lt;/code> 而非 &lt;code>context.go('/enrollment')&lt;/code> — &lt;code>push&lt;/code> 保留返回堆疊讓使用者配對後按 back 回首頁；&lt;code>go&lt;/code> 替換整個路由堆疊、沒有 back。這個決策影響使用者的導航體驗，但也是事後才想到的。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>導航流從操作盤點反推&lt;/strong>：每個 UC（用例）的主入口在哪？首頁應該是哪些 UC 的 hub？列出來，確認每個 UC 至少有一條從首頁可達的路徑。&lt;/li>
&lt;li>&lt;strong>路由可達性檢查&lt;/strong>：router 定義的每個路由都應該從 UI 可達。不可達的路由要嘛是遺漏入口（本案例），要嘛是應該刪除的死路由。可以寫一個 lint 檢查。&lt;/li>
&lt;li>&lt;strong>首次 vs 日常使用者的 UX 區分&lt;/strong>：首次使用者需要 onboarding 流程（配對 → 連線），日常使用者只需要連線。兩種入口都要在首頁可見，但可以用視覺層級區分主要/次要。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計完整導航流 → &lt;a href="https://tarrragon.github.io/blog/ux-design/05-navigation-patterns/" data-link-title="模組五：導航模式" data-link-desc="Push/pop stack、GoRouter 命名路由、tab bar、drawer — 導航方法選擇是設計決策">模組五：導航模式&lt;/a>&lt;/li>
&lt;li>想檢查畫面狀態矩陣的退出路徑 → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出&lt;/a>&lt;/li>
&lt;li>想做路由可達性自動化檢查 → 待補：Flutter GoRouter 路由可達性 lint&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明導航流設計必須覆蓋所有操作情境的入口，不只是最常用的那個。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 首頁在 W2-001 修復前只有一個按鈕：Connect Terminal（對應 UC-02 日常連線）。配對功能（UC-01 首次配對）沒有入口 — <code>EnrollmentScreen</code> 和 <code>QrScannerScreen</code> 都存在且可運作，但首頁沒有按鈕導航過去。</p>
<p>Router 定義了三條路由，全部可存取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">GoRouter</span><span class="p">(</span><span class="nl">routes:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">HomeScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/enrollment&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">EnrollmentScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/terminal&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">TerminalScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">]);</span></span></span></code></pre></div><p>但 HomeScreen 只有一個 <code>context.go('/terminal')</code> 按鈕，<code>/enrollment</code> 路由存在但從 UI 無法到達。</p>
<p>W2-001 修復加入 <code>OutlinedButton.icon</code> 連結到 <code>/enrollment</code>，並用 <code>context.push</code>（非 <code>context.go</code>）讓配對完成後能返回首頁。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>影響</td>
          <td>首次使用者無法配對（功能存在但入口缺失）</td>
      </tr>
      <tr>
          <td>修復</td>
          <td>加一個 <code>OutlinedButton</code> + <code>context.push('/enrollment')</code></td>
      </tr>
      <tr>
          <td>根因</td>
          <td>導航流只設計了「日常連線」入口，遺漏「首次配對」入口</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>操作盤點有四個操作，首頁只有一個入口</strong>。操作盤點段列出四個操作：配對、連線、輪替、啟停。首頁應該是這四個操作的導航 hub，至少要有「配對」和「連線」兩個入口（輪替和啟停是主機端操作，不需要 app 入口）。只放 Connect Terminal 等於假設「使用者已經配對過」。</p>
</li>
<li>
<p><strong>路由存在但 UI 不可達 = 死程式碼的 UX 版本</strong>。<code>/enrollment</code> 路由在 router 裡定義了，<code>EnrollmentScreen</code> 也完整實作了，但使用者從 UI 無法觸及。這跟寫了函式但沒有呼叫者一樣 — 功能正確但不可存取。</p>
</li>
<li>
<p><strong><code>go</code> vs <code>push</code> 的語意差異影響 UX</strong>。W2 修復用 <code>context.push('/enrollment')</code> 而非 <code>context.go('/enrollment')</code> — <code>push</code> 保留返回堆疊讓使用者配對後按 back 回首頁；<code>go</code> 替換整個路由堆疊、沒有 back。這個決策影響使用者的導航體驗，但也是事後才想到的。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>導航流從操作盤點反推</strong>：每個 UC（用例）的主入口在哪？首頁應該是哪些 UC 的 hub？列出來，確認每個 UC 至少有一條從首頁可達的路徑。</li>
<li><strong>路由可達性檢查</strong>：router 定義的每個路由都應該從 UI 可達。不可達的路由要嘛是遺漏入口（本案例），要嘛是應該刪除的死路由。可以寫一個 lint 檢查。</li>
<li><strong>首次 vs 日常使用者的 UX 區分</strong>：首次使用者需要 onboarding 流程（配對 → 連線），日常使用者只需要連線。兩種入口都要在首頁可見，但可以用視覺層級區分主要/次要。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計完整導航流 → <a href="/blog/ux-design/05-navigation-patterns/" data-link-title="模組五：導航模式" data-link-desc="Push/pop stack、GoRouter 命名路由、tab bar、drawer — 導航方法選擇是設計決策">模組五：導航模式</a></li>
<li>想檢查畫面狀態矩陣的退出路徑 → <a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出</a></li>
<li>想做路由可達性自動化檢查 → 待補：Flutter GoRouter 路由可達性 lint</li>
</ul>
]]></content:encoded></item></channel></rss>