Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d98863
Merge pull request #2 from frobel0520/dev
frobel0520 Aug 14, 2026
ba13cf2
release: promote workshop curriculum updates to main (#49)
frobel0520 Aug 16, 2026
a5be461
release: promote build and environment workshops
frobel0520 Aug 16, 2026
1faca20
release: reconcile dev with main for SQL-01
frobel0520 Aug 18, 2026
29de806
Merge pull request #56 from frobel0520/codex/release-sql-20260818
frobel0520 Aug 18, 2026
38685bb
Merge all-push CI workflow to main
frobel0520 Aug 21, 2026
2838d34
release: ship Core 19/19 to main (#90)
frobel0520 Aug 23, 2026
2f3f6a2
release: close M6 audit (#92)
frobel0520 Aug 23, 2026
d226b5b
release: sync final Pages evidence (#94)
frobel0520 Aug 23, 2026
3b098ff
fix(rest): fall back when browsing unrelated code file
frobel0520 Aug 23, 2026
d681fc8
fix(build): add portable Pages build profile
frobel0520 Aug 23, 2026
ecc4341
fix(postgresql): create GIN index before explain
frobel0520 Aug 23, 2026
c982356
fix(unit): align before-fix discount fixtures
frobel0520 Aug 23, 2026
1936e73
fix(git): enforce release command allowlist
frobel0520 Aug 23, 2026
60fa74d
fix(rest): enforce sequential lifecycle stages
frobel0520 Aug 23, 2026
f806064
fix(deploy): cover base path mismatch scenario
frobel0520 Aug 23, 2026
79abd53
fix(cicd): cover dependency install failure
frobel0520 Aug 23, 2026
a2618c7
fix(logs): cover all declared redacted fields
frobel0520 Aug 23, 2026
a54468f
fix(a11y): complete file tab semantics
frobel0520 Aug 23, 2026
7617af3
merge: reconcile main into dev
frobel0520 Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ jobs:
run: npm ci
- name: Test and build
working-directory: frontend
env:
VITE_BASE: /${{ github.event.repository.name }}/
run: npm test && npm run build
run: npm test && npm run build:pages
- name: Publish gh-pages branch
uses: peaceiris/actions-gh-pages@v4
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Thumbs.db
.env
.env.*
!.env.example
!frontend/.env.pages
8 changes: 8 additions & 0 deletions docs/accessibility-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
- `styles.css` 已確認含 `max-width: 720px` responsive rules 與 `prefers-reduced-motion` rules。
- 手動驗收已通過:使用實體鍵盤確認 `Tab`/`Enter`/`Space` 可操作 Lesson/Lab controls,terminal submit、reset、錯誤回饋與返回課程地圖流程可完成;reduced-motion 偏好下內容與操作仍完整。`GIT-REVIEW` 已通過。

### 2026-08-23:M6 delivery topic browser smoke

- CI/CD Lesson → Lab route 與 Deploy Lesson → Lab route 可達;兩個 Lab 的 native buttons、command input、`aria-live` feedback、progressbar 與 reset 都存在。
- CI/CD 已驗證 green pipeline 與 test failure;failure state 保留 `lint`/`build: not-run`、required check failed 與 merge gate blocked。
- Deploy 已驗證 green release、artifact blocked、probe failure → rollback、release record、Pages pointer 與 reset/replay completion。
- CI/CD、Deploy 在 390×844 viewport 下 document width 為 375,mobile menu 可開關,command input 可取得 focus;兩個 topic styles 都含 `prefers-reduced-motion` rule。
- M6 full regression:83 test files / 265 tests、TypeScript lint、GitHub Pages base-path build 與 `git diff --check` 通過。完整 release 結論見 [`release-audit.md`](./release-audit.md)。

## 1. Keyboard 與焦點

- [ ] 所有互動控制項都能以 `Tab`、`Shift+Tab`、`Enter` 或 `Space` 操作。
Expand Down
4 changes: 2 additions & 2 deletions docs/build-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
```text
cat package.json
→ npm run lint
VITE_BASE=/software-engineering-workshop/ npm run build
→ npm run build:pages
→ ls dist
→ npm run preview
→ BUILD complete
Expand All @@ -21,7 +21,7 @@ cat package.json

- `lint` 是 TypeScript gate;先擋住 source 層的型別錯誤。
- `build` 產出 `dist/index.html` 與 hashed assets。
- `VITE_BASE=/software-engineering-workshop/` 對應 GitHub Pages project site 的 nested path。
- `npm run build:pages` 透過 `--mode pages` 載入 `frontend/.env.pages`,其 `VITE_BASE=/software-engineering-workshop/` 對應 GitHub Pages project site 的 nested path。
- `preview` 服務 `dist/`,不是 dev server,也不是 production server。
- `dist/` 是可發布 artifact,不應被當成手寫 source。

Expand Down
10 changes: 6 additions & 4 deletions docs/cicd-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
2. 讀懂 workflow trigger,知道 push、pull request 與手動 dispatch 會在什麼 ref 上啟動哪些 fixture job。
3. 理解 checkout、Node setup/cache、install、test、lint、build 的順序與 gate 責任。
4. 以 required check 判斷 pull request 是否可以進入 merge,而不是只看某個單獨 step 的綠色結果。
5. 分辨 test failure 與 build failure 的 evidence,知道下游 step 在 job failure 後不應被偽造為已執行。
5. 分辨 install、test 與 build failure 的 evidence,知道下游 step 在 job failure 後不應被偽造為已執行。
6. 用固定的 success/failure scenario 重跑 pipeline,理解 reset、retry 與 deterministic feedback 的關係;本 Lab 不連接真實 GitHub Actions。

## 2. 教學邊界
Expand Down Expand Up @@ -45,7 +45,7 @@ CI/CD Lesson
→ run build
→ publish required check
→ 判斷 mergeable/blocked
→ 完成 success、test failure、build failure 三個 scenarios
→ 完成 success、install failure、test failure、build failure 四個 scenarios
→ reset 後重跑 green pipeline regression
→ 標記 CI/CD topic complete
```
Expand All @@ -67,6 +67,7 @@ CI/CD Lesson
| Scenario | Fixture input | Fixture outcome | 教學重點 |
| --- | --- | --- | --- |
| `pull-request-green` | event `pull_request`、base `dev`、test/lint/build 全部 pass | `frontend` required check passed,merge gate `mergeable` | 完整 CI gate 通過才表示 pull request 可以進入 merge。 |
| `pull-request-install-failure` | event `pull_request`、base `dev`、`npm ci` fixture failed | install `failed`;test/lint/build `not-run`;required check failed;merge gate blocked | install 是第一個依賴 boundary,不能用舊的 node_modules 或下游輸出假裝通過。 |
| `pull-request-test-failure` | event `pull_request`、base `dev`、test fixture failed | test `failed`;lint/build `not-run`;required check failed;merge gate blocked | 不把下游未執行的 steps 畫成綠色,先保留第一個 failure boundary。 |
| `pull-request-build-failure` | event `pull_request`、base `dev`、test/lint pass、build fixture failed | build `failed`;required check failed;merge gate blocked | test 與 lint 綠色不能掩蓋 production build failure。 |

Expand Down Expand Up @@ -204,8 +205,9 @@ CicdLabState {

只有下列條件全部成立時,CI/CD Lab 才算完成:

- 三個 required scenarios 都完成各自的 terminal outcome。
- 四個 required scenarios 都完成各自的 terminal outcome。
- `pull-request-green` 顯示完整 stage、required check passed 與 mergeable。
- `pull-request-install-failure` 顯示 install failed、test/lint/build not-run、required check failed 與 merge gate blocked。
- `pull-request-test-failure` 顯示 test failed、lint/build not-run、required check failed 與 merge gate blocked。
- `pull-request-build-failure` 顯示 test/lint passed、build failed、artifact missing、required check failed 與 merge gate blocked。
- reset 後重跑 green pipeline,trigger、ref、stage status、check、merge gate 與 feedback 與第一次一致。
Expand All @@ -224,7 +226,7 @@ CicdLabState {

## 11. CICD-01 驗收

- 文件明確描述 CI/CD boundary、trigger/ref、workflow fixture、九個 observable stages、三個 scenarios、failure feedback、completion 與 out-of-scope。
- 文件明確描述 CI/CD boundary、trigger/ref、workflow fixture、九個 observable stages、四個 scenarios、failure feedback、completion 與 out-of-scope。
- `CICD-02` 可依本文件撰寫 lesson 與 workflow fixture,不需要重新決定 job step、required check 或 failure semantics。
- `CICD-03` 可依本文件建立純 simulator;不需要真實 GitHub Actions、runner、network、secret 或 shell。
- `CICD-04` 可依本文件設計 Lab 的 trigger selector、step evidence、required check、merge gate、reset、keyboard、mobile 與 reduced-motion interaction。
Expand Down
18 changes: 10 additions & 8 deletions docs/deploy-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
2. 讀懂部署 workflow 的 `main` trigger、`workflow_dispatch`、`frontend/dist` 與 `gh-pages` publish branch。
3. 理解 release source、版本識別、Pages base path 與 artifact provenance 的關係。
4. 在 artifact 缺失或部署驗證失敗時,保留 blocked/failed evidence,不把失敗版本誤標成 live。
5. 以固定 release scenario 練習成功發布、artifact 缺失與 rollback 到上一個可用版本。
5. 以固定 release scenario 練習成功發布、artifact 缺失、base path mismatch 與 rollback 到上一個可用版本。
6. 用部署狀態、live probe、release record 與 rollback evidence 判斷一次交付是否真的完成。

## 2. 教學邊界
Expand All @@ -26,6 +26,8 @@ fixture 以目前 repository 的 `.github/workflows/deploy-pages.yml` 為輸入

本主題不重新定義 CI 的 test/lint/build gate;它只接收一個固定的 CI passed/artifact state,專注 release、publish、verify、record 與 rollback。真實 domain、DNS、CDN、帳號權限與維運告警留給後續產品決策。

Pages build 使用 `frontend/.env.pages` 與 `npm run build:pages` 載入 repository-specific `VITE_BASE`;這個公開路徑設定不依賴 shell-specific environment assignment。

## 3. Lesson/Lab Pageflow

```text
Expand All @@ -45,7 +47,7 @@ Deploy Lesson
→ verify deployment
→ record release
→ evaluate release/rollback
→ 完成 success、missing artifact、rollback 三個 scenarios
→ 完成 success、missing artifact、base path mismatch、rollback 四個 scenarios
→ reset 後重跑 green release regression
→ 標記 Deploy topic complete
```
Expand All @@ -67,6 +69,7 @@ Deploy Lesson
| --- | --- | --- | --- |
| `main-pages-success` | source `main`、CI passed、`dist` verified、Pages base path verified | `gh-pages` updated to `release-2026.08.23`;live probe 200;release `verified` | artifact、publish 與 live verification 必須全部完成,才是成功部署。 |
| `missing-artifact-blocked` | source `main`、CI passed、`dist` missing | publish blocked;`gh-pages` 保持上一個 verified version;release `blocked` | build/artifact 缺失時不可更新 Pages,也不可假裝 live。 |
| `base-path-mismatch-blocked` | source `main`、CI passed、`dist` verified、Pages base path mismatch | publish blocked;`gh-pages` 保持上一個 verified version;release `blocked` | artifact 存在不代表資產可用;repository path 與 Vite base path 不一致時必須阻擋發布。 |
| `rollback-after-probe-failure` | source `main`、new artifact verified、publish succeeded、live probe failed | release `release-2026.08.23` failed;rollback 到 `release-2026.08.16`;live probe 200 | rollback 指向上一個可用版本,保留失敗版本與原因。 |

每個 scenario 都必須讓學習者看見:release source、version、CI result、artifact、base path、Pages branch pointer、publish result、deployment status、live URL/probe、release record 與 rollback evidence;失敗時要看見未更新或已回復的邊界。
Expand Down Expand Up @@ -108,10 +111,8 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- run: npm ci
working-directory: frontend
- run: npm test && npm run build
- run: npm test && npm run build:pages
working-directory: frontend
env:
VITE_BASE: /${{ github.event.repository.name }}/
- uses: peaceiris/actions-gh-pages@v4
with:
publish_dir: ./frontend/dist
Expand Down Expand Up @@ -140,7 +141,7 @@ inspect-workflow
| inspect workflow | `cat .github/workflows/deploy-pages.yml` | main trigger、workflow_dispatch、dist、gh-pages 可見 | 未 inspect 時不可選擇 release source |
| select release | `main → release-2026.08.23` | source、candidate version 與 previous verified version 可見 | 非 main source 應被阻擋 |
| verify CI artifact | `artifact: frontend/dist` | CI passed、dist exists、artifact provenance 可見 | artifact missing 時 publish 不可更新 gh-pages |
| verify Pages base | `VITE_BASE=/software-engineering-workshop/` | base path 與 repository path 一致 | base path mismatch 時 deployment blocked |
| verify Pages base | `cat frontend/.env.pages` | profile 的 base path 與 repository path 一致 | base path mismatch 時 deployment blocked |
| publish Pages | `publish → gh-pages` | gh-pages pointer 指向 candidate version | artifact/base path 不完整時 branch 保持 previous version |
| verify deployment | `probe /software-engineering-workshop/` | live status 200、candidate version 可觀測 | probe failure 要保留 failed version,不宣稱 live |
| record release | `record release` | version、source、artifact、URL、status 完整 | 沒有 deployment evidence 時不可寫 verified record |
Expand Down Expand Up @@ -206,9 +207,10 @@ DeployLabState {

只有下列條件全部成立時,Deploy Lab 才算完成:

- 三個 required scenarios 都完成各自的 terminal outcome。
- 四個 required scenarios 都完成各自的 terminal outcome。
- `main-pages-success` 顯示 candidate artifact、gh-pages publish、live status 200 與 verified release record。
- `missing-artifact-blocked` 顯示 artifact missing、publish blocked、Pages pointer 保持 previous verified version 與 blocked record。
- `base-path-mismatch-blocked` 顯示 artifact verified、base path mismatch、publish blocked、Pages pointer 保持 previous verified version 與 blocked record。
- `rollback-after-probe-failure` 顯示 candidate probe failed、failed release record、rollback version、Pages pointer 回到 previous verified version 與 rolled-back record。
- reset 後重跑 green release,source、version、artifact、Pages pointer、probe、record 與 feedback 與第一次一致。
- 完成後使用 `se-workshop-deploy-complete` 保存進度。
Expand All @@ -226,7 +228,7 @@ DeployLabState {

## 11. DEPLOY-01 驗收

- 文件明確描述 release/artifact/Pages/live probe/rollback boundary、workflow fixture、八個 observable stages、三個 scenarios、failure feedback、completion 與 out-of-scope。
- 文件明確描述 release/artifact/Pages/live probe/rollback boundary、workflow fixture、八個 observable stages、四個 scenarios、failure feedback、completion 與 out-of-scope。
- `DEPLOY-02` 可依本文件撰寫 lesson 與 workflow/artifact fixture,不需要重新決定 release source、version 或 Pages semantics。
- `DEPLOY-03` 可依本文件建立純 simulator;不需要真實 GitHub Pages、network、secret、shell 或 runner。
- `DEPLOY-04` 可依本文件設計 Lab 的 release selector、stage evidence、live probe、rollback、reset、keyboard、mobile 與 reduced-motion interaction。
Expand Down
7 changes: 5 additions & 2 deletions docs/logs-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ const baseRequest = {
method: "POST",
route: "/orders",
authorization: "Bearer test-secret-001",
password: "workshop-password-001",
accessToken: "access-token-001",
cookie: "session=workshop-session-001",
email: "learner@example.test",
payload: { sku: "book", quantity: 1 },
};
Expand All @@ -131,7 +134,7 @@ Fixture rules:
- 每個 scenario 至少先產生 `request.received`,再產生一筆固定 terminal event;兩筆 event 的 `correlationId` 必須相同。
- `request.received` 的 `level` 固定為 `debug`、`source` 固定為 `api`、`outcome` 固定為 `started`。
- `request-success` 只能改變 terminal success outcome;不得同時加入 timeout 或 validation failure。
- `validation-rejected` 只能因缺少 `amount` 被拒絕;不得輸出 `authorization`、`email` 或完整 payload。
- `validation-rejected` 只能因缺少 `amount` 被拒絕;不得輸出任何敏感欄位或完整 payload。
- `dependency-timeout` 只能因固定 `payment-provider` 在 `3000ms` 逾時失敗;不得產生成功付款或訂單副作用。
- 相同 initial state 加上相同 event sequence,必須得到相同的 events、feedback、terminal outcome 與 completion 結果。
- 不可使用目前時間、random UUID、網路回應、真實 provider、瀏覽器 local state 以外的外部輸入。
Expand All @@ -141,7 +144,7 @@ Fixture rules:
Redaction 必須在 event 格式化前完成,而不是只在畫面上用 CSS 或字串替換遮住結果:

- `authorization`、`password`、`accessToken`、`cookie` 與 `email` 的 raw value 不得出現在 `message`、`context`、`redactedFields` 或 serialized event output。
- `redactedFields` 必須至少記錄本 fixture 中被移除的 `authorization` 與 `email`,並以固定順序輸出。
- `redactedFields` 必須記錄本 fixture 中被移除的 `authorization`、`password`、`accessToken`、`cookie` 與 `email`,並以固定順序輸出。
- 安全輸出只能使用 allowlist 欄位;不得透過 `JSON.stringify(baseRequest)` 再事後刪除一部分文字。
- 任一 raw sensitive value 被找到時,scenario 必須進入 `redaction-failed` feedback,不能完成,也不能把該 event 當作有效證據。
- reset 後 redaction 結果必須與第一次執行完全一致。
Expand Down
4 changes: 2 additions & 2 deletions docs/release-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Core 19 個 topic 全部為 `ready`,Extension 2 個 topic 維持獨立完成

- `npm --prefix frontend test`:83 test files / 265 tests passed。
- `npm --prefix frontend run lint`:passed。
- `VITE_BASE=/software-engineering-workshop/ npm --prefix frontend run build`:passed。
- `npm --prefix frontend run build:pages`:passed。
- Pages build output `frontend/dist/index.html` 使用 `/software-engineering-workshop/assets/...` base path。
- `git diff --check`:passed。

## RELEASE-006:release、遠端狀態與 Pages

- `git merge-tree --write-tree origin/main origin/dev`:clean,未發現 merge conflict
- 初始 `origin/main` ↔ `origin/dev` release merge-tree(功能 release PR #90 前)為 clean;#91 後新增的 closeout 文件在 `main` 與 `dev` 間產生預期的 docs-only add/add/content conflict,已在 `feature/m6-closeout-release` 手動解決,未涉及 source code
- M5 的 feature PR(Docker、CI/CD、Deploy)均經 `feature/* → dev`、required checks 與 squash merge。
- Release PR #90 已 squash merge 到 `main`,merge commit:`2838d34`。
- Core release Publish workflow #16:`32611711879`,Success。
Expand Down
2 changes: 2 additions & 0 deletions frontend/.env.pages
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Public, repository-specific base path for the GitHub Pages build profile.
VITE_BASE=/software-engineering-workshop/
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:pages": "tsc -b && vite build --mode pages",
"lint": "tsc --noEmit",
"review": "node ../.github/scripts/ollama-review.mjs",
"preview": "vite preview",
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/components/tab-navigation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { tabIndexForKey } from "./tab-navigation";

describe("tab keyboard navigation", () => {
it("wraps through horizontal and vertical arrow keys", () => {
expect(tabIndexForKey("ArrowRight", 0, 3)).toBe(1);
expect(tabIndexForKey("ArrowDown", 2, 3)).toBe(0);
expect(tabIndexForKey("ArrowLeft", 0, 3)).toBe(2);
expect(tabIndexForKey("ArrowUp", 2, 3)).toBe(1);
});

it("jumps to the first or last tab with Home and End", () => {
expect(tabIndexForKey("Home", 2, 3)).toBe(0);
expect(tabIndexForKey("End", 0, 3)).toBe(2);
});

it("ignores unsupported keys and invalid tab positions", () => {
expect(tabIndexForKey("Enter", 1, 3)).toBeNull();
expect(tabIndexForKey("ArrowRight", -1, 3)).toBeNull();
expect(tabIndexForKey("ArrowRight", 0, 0)).toBeNull();
});
});
8 changes: 8 additions & 0 deletions frontend/src/components/tab-navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function tabIndexForKey(key: string, currentIndex: number, tabCount: number): number | null {
if (tabCount <= 0 || currentIndex < 0 || currentIndex >= tabCount) return null;
if (key === "Home") return 0;
if (key === "End") return tabCount - 1;
if (key === "ArrowRight" || key === "ArrowDown") return (currentIndex + 1) % tabCount;
if (key === "ArrowLeft" || key === "ArrowUp") return (currentIndex - 1 + tabCount) % tabCount;
return null;
}
32 changes: 32 additions & 0 deletions frontend/src/git/release-simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,38 @@ describe("Git cowork release simulator", () => {
expect(result.output.join(" ")).toContain("Fork");
});

it("rejects commands with unsupported suffixes instead of matching by prefix", () => {
const initial = createInitialGitReleaseState();
const invalidClone = runGitReleaseCommand(initial, "git clone --mirror <your-url>");
const invalidCommit = runGitReleaseCommand(initial, "git commit --amend");

expect(invalidClone.accepted).toBe(false);
expect(invalidClone.state).toBe(initial);
expect(invalidCommit.accepted).toBe(false);
expect(invalidCommit.state).toBe(initial);
});

it("rejects force push after the workflow reaches the push step", () => {
const beforePush = runCommands([
"Fork repository",
"git clone <your-url>",
"git checkout -b feature/profile",
"git stash",
"git stash pop",
"git diff",
"git add src/profile.ts",
'git commit -m "add profile page"',
"git fetch origin",
"git pull --rebase origin dev",
"git rebase origin/dev",
"git cherry-pick a1b2c3d",
]);
const result = runGitReleaseCommand(beforePush, "git push --force");

expect(result.accepted).toBe(false);
expect(result.state).toBe(beforePush);
});

it("supports GitLab Merge Request terminology", () => {
let state = createInitialGitReleaseState();
state = runGitReleaseEvent(state, { type: "set-provider", provider: "gitlab" }).state;
Expand Down
Loading
Loading