先问“接下来会发生什么”
+GuidedStep 把操作、观察位置、预测问题、预期日志和胜利条件放在同一张训练卡里。
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..735ca52 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,62 @@ +name: Verify and deploy public case + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Verify public case and workflow pins + run: make verify-showcase + + package: + if: github.event_name != 'pull_request' + needs: verify + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Configure Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: ./docs + + deploy: + if: github.event_name != 'pull_request' + needs: package + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/Makefile b/Makefile index 263a424..efcc4c1 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ APP_PATH := $(DERIVED_DATA)/Build/Products/$(CONFIGURATION)-iphonesimulator/UIKi BUNDLE_ID := com.example.UIKitLifecycleDemo XCBEAUTIFY ?= xcbeautify -.PHONY: build test-ui run open logs clean +.PHONY: build test-ui run open logs clean verify-showcase build: @mkdir -p "$(LOG_DIR)" @@ -75,3 +75,7 @@ logs: clean: @xcodebuild -project "$(PROJECT)" -scheme "$(SCHEME)" -derivedDataPath "$(DERIVED_DATA)" clean >/dev/null @rm -rf "$(DERIVED_DATA)" "$(LOG_DIR)" + +verify-showcase: + @python3 scripts/audit-showcase.py + @python3 scripts/verify-actions-pinned.py diff --git a/README.md b/README.md index 88818f3..410668e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,54 @@ -# UIKitLifecycleDemo +# UIKit Lifecycle Lab -一个纯 UIKit、纯代码的中文学习实验室。它不是提醒事项业务 App,而是一台“UIKit 显微镜”:你可以在 App 内 Logs、Xcode Console、断点和 Call Stack 里观察 UIKit 什么时候自动调用生命周期、delegate、target-action、closure 回传、snapshot 刷新和 cell 复用。 +一个纯 UIKit、纯代码的中文学习实验室。它不是提醒事项业务 App,而是一台“UIKit 显微镜”:用 Guided Steps、结构化日志、Xcode Call Stack 和 UI Test,观察 UIKit 什么时候自动调用生命周期、delegate、target-action、closure 回传、snapshot 刷新和 cell 复用。 + +> **English summary:** A code-first UIKit learning lab that makes framework-driven behavior observable through guided experiments, structured logs, call stacks, and UI tests. + +[公开案例页](https://estelledc.github.io/UIKitLifecycleDemo/) · [Jason Hub](https://estelledc.github.io/) · [About](https://estelledc.github.io/about/) · [Resume](https://estelledc.github.io/resume/) + +## 公开案例 + +### Problem + +UIKit 的很多关键方法不是业务代码直接调用的。只读 `viewDidLoad`、`didSelectItemAt` 或 `saveReminder` 的定义,很容易把框架生命周期、delegate 回调、按钮事件和业务 closure 混在一起。 + +### Role + +- **Jason**:定义学习目标、选择观察机制、编排验证路线,并用真实 Xcode / Simulator 结果验收。 +- **Codex / AI**:辅助搭建 Demo、实现结构化日志与文档、迭代自动化测试。 +- **Xcode / Simulator / XCTest**:提供最终运行证据;README 中的预期顺序不替代真实构建与观察。 + +### System + +```text +Predict -> Run -> Observe -> Verify -> Recap + Guide App Logs Stack/Test 复盘 +``` + +1. `GuidedStep` 把操作、观察位置、预测问题、预期日志和胜利条件放在同一张训练卡里。 +2. `DemoLogStore` 同时保存结构化事件并输出 Xcode Console。 +3. App 内 Log Panel 支持分类、搜索、关键事件、暂停滚动与复制。 +4. Call Stack 回答“谁触发”,UI Test 检查关键用户流程。 + +### Evidence + +| 可核验事实 | 当前仓库依据 | +|---|---| +| 9 个引导步骤 | `UIKitLifecycleDemo/GuidedExperiment.swift` | +| 14 类结构化日志 | `UIKitLifecycleDemo/DemoLogStore.swift` | +| 2 条 UI Test 流程 | `UIKitLifecycleDemoUITests/UIKitLifecycleDemoUITests.swift` | +| 纯 UIKit、纯代码入口 | `SceneDelegate -> UINavigationController -> ReminderListViewController` | +| 可重复构建 | `Makefile` 中的 `build / run / test-ui` | + +这些数字描述当前实现范围,不代表学习效果。 + +### Limitations + +- 这是学习实验室,不是生产级提醒事项 App。 +- 不包含网络、持久化、EventKit 或真实业务状态。 +- Guide 描述预期观察,不会生成或伪造日志。 +- 两条 UI Test 只覆盖核心编辑流程和 Guide / Logs 入口,尚未覆盖全部高级实验。 +- 项目不声称仅凭阅读或运行 Demo 就能掌握 UIKit。 ## 5 分钟 Guided Tour @@ -20,18 +68,18 @@ 你最终要亲眼确认: - `viewDidLoad` 只在 view 第一次加载后执行。 -- `viewWillAppear / viewDidAppear` 每次页面重新出现都会执行。 +- `viewWillAppear / viewDidAppear` 在页面重新出现时再次执行。 - `didSelectItemAt` 是 UIKit 收到 cell 点击后的 delegate 回调。 -- `saveReminder` 是按钮事件通过 target-action 或 UIAction 触发。 +- `saveReminder` 由 target-action 或 UIAction 触发。 - `onSave closure` 是 Detail 把编辑结果交回 List 的业务通道。 -- Detail pop 后出现 `deinit`,说明详情页释放了。 +- Detail pop 后出现 `deinit`,说明该详情页实例释放。 -## 如何运行 +完整说明见 [docs/guided-learning.md](docs/guided-learning.md)。 + +## 本地运行 ```bash -cd explorations/_active/UIKitLifecycleDemo make build -make open make run make test-ui ``` @@ -51,79 +99,63 @@ make build SIMULATOR_NAME="iPhone 16 Pro" 环境准备见 [docs/setup.md](docs/setup.md)。 -## 如何打开 Logs - -像看慢放录像一样,Logs 面板让你不用追着 Xcode Console 跑。 - -操作: +## 如何观察 -1. 启动 App。 -2. 点击右上角 `Learn`。 -3. 点击 `Logs`。 -4. 用 Filter 一次选择一个分类,例如先选 `Lifecycle`,再分别切到 `Delegate`、`Action`、`Closure`、`Snapshot`、`Cell`、`NavStack`。 -5. 打开 `Only Key Events` 只看关键事件。 -6. 点击 `Pause Scroll` 暂停自动滚动;再次点击 `Resume Scroll` 恢复自动滚动。 -7. 点击 `Copy` 复制当前可见日志给 ChatGPT 分析。 +### App 内 Logs -注意:当前 Log Panel 的 Filter 一次只能选择一个分类。需要组合观察时,可以保持 `All` 并使用 Search 搜索 `viewDidLoad`、`saveReminder` 等关键词,或分别切换分类。 +1. 点击 `Learn -> Logs`。 +2. 用 Filter 分别观察 `Lifecycle`、`Delegate`、`Action`、`Closure`、`Snapshot`、`Cell`、`NavStack`。 +3. 打开 `Only Key Events` 只看关键事件。 +4. 用 `Pause Scroll / Resume Scroll` 控制自动滚动。 +5. 点击 `Copy` 复制当前可见日志。 详细说明见 [docs/log-panel.md](docs/log-panel.md)。 -## 如何使用 Guide +### Xcode Call Stack -Guide 是带学模式。每张卡片都有: +Logs 负责看顺序,Call Stack 负责看“是谁调用的”。建议断点: -- 当前做什么 -- 应该看哪里 -- 操作前预测 -- 预期日志 -- 完成后理解问题 -- 一句话复盘 -- 胜利条件 +- `ReminderListViewController.viewDidLoad` +- `ReminderListViewController.viewWillAppear` +- `ReminderListViewController.collectionView(_:didSelectItemAt:)` +- `ReminderDetailViewController.viewDidLoad` +- `ReminderDetailViewController.saveReminder` -详细说明见 [docs/guided-learning.md](docs/guided-learning.md)。 +断点命中后在 LLDB 输入 `bt`,寻找 `UIKitCore`、`UIViewController`、`UICollectionViewController`、`UINavigationController` 与 `UIApplication sendAction`。完整步骤见 [docs/call-stack-teaching.md](docs/call-stack-teaching.md)。 -## 完整实验目录 +## 实验入口 入口:`Learn -> Experiments` | 实验 | 观察重点 | |---|---| -| Show / Push / Present | 展示方式、navigation stack、pop/dismiss | +| Show / Push / Present | 展示方式、navigation stack、pop / dismiss | | Snapshot Full Apply / Reload / Reconfigure | diffable snapshot 如何刷新列表 | | Toggle Save | target-action 与 UIAction 的差异 | -| Toggle Closure | weak/strong capture 与 deinit / Memory Graph | +| Toggle Closure | weak / strong capture 与 `deinit` / Memory Graph | | Load 50 Reuse Items | cell init、updateConfiguration、prepareForReuse | | String Identity Experiment | 重复 String identifier 的风险 | -| Manual UIViewController Version | 手动创建 collectionView 的对照实验 | - -## Xcode Call Stack 验证 - -Logs 负责看顺序,Call Stack 负责看“是谁调用的”。 - -建议断点: +| Manual UIViewController Version | 手动创建 collection view 的对照实验 | -- `ReminderListViewController.viewDidLoad` -- `ReminderListViewController.viewWillAppear` -- `ReminderListViewController.collectionView(_:didSelectItemAt:)` -- `ReminderDetailViewController.viewDidLoad` -- `ReminderDetailViewController.saveReminder` +## 公开展示验证 -断点命中后,在 LLDB 输入: +GitHub Pages 源文件位于 `docs/`,不改动核心 UIKit 演示代码。执行: -```lldb -bt +```bash +make verify-showcase +git diff --check ``` -重点找: +其中 `make verify-showcase` 会检查: -- `UIKitCore` -- `UIViewController` -- `UICollectionViewController` -- `UINavigationController` -- `UIApplication sendAction` +- Problem / Role / System / Evidence / Limitations 与 Jason / AI 边界。 +- canonical、Open Graph、Twitter Card、JSON-LD 和分享图片尺寸。 +- 站内链接、锚点、图片 alt 与尺寸属性。 +- 9 / 14 / 2 三项展示数字是否仍与 Swift 源码一致。 +- Jason DS v2、reduced-motion、焦点样式与隐私标记。 +- 所有第三方 GitHub Actions 是否固定到完整 40 位 commit SHA。 -详细步骤见 [docs/call-stack-teaching.md](docs/call-stack-teaching.md)。 +分享图需要重建时,可先安装 Pillow,再运行 `python3 scripts/generate-showcase-assets.py`。 ## 项目结构 @@ -143,41 +175,21 @@ UIKitLifecycleDemo/ StringIdentityExperimentViewController.swift ManualCollectionViewController.swift UIKitLifecycleDemoUITests/ - UIKitLifecycleDemoUITests.swift docs/ + index.html # 公开案例页 + assets/ # 页面样式、真实模拟器截图、Jason DS、分享图 +scripts/ + audit-showcase.py + verify-actions-pinned.py Makefile CHANGELOG.md LICENSE ``` -入口链路: - -```text -SceneDelegate --> UIWindow --> UINavigationController --> ReminderListViewController -``` - -## 学习方法 - -日常类比:UIKit 像舞台管理员。你写的 view controller 不是自己上台,而是等舞台管理员通知“该加载 view 了”“该出现了”“按钮被点了”“cell 被选中了”。 - -技术定义: - -- 生命周期:UIKit 根据页面状态自动调用。 -- delegate:UIKit 把用户操作回调给你。 -- target-action:UIKit 根据控件事件调用 selector/action。 -- closure:业务代码自己设计的数据回传通道。 - -Codex 学习流程见: - -- [docs/codex-learning-workflow.md](docs/codex-learning-workflow.md) -- [docs/agent-flow.md](docs/agent-flow.md) -- [docs/computer-use-sop.md](docs/computer-use-sop.md) - ## 版本范围 - `1.0.0`:基础 UIKit 生命周期、collection view、delegate、target-action、closure。 -- `1.1.0`:扩展可观测实验。 -- `1.2.0`:App 内 Logs、Guided Learning、慢速 Computer Use SOP。 +- `1.1.0`:扩展可观察实验。 +- `1.2.0`(Building):App 内 Logs、Guided Learning 与 Computer Use SOP;尚未正式发布。 + +项目代码使用 MIT License。课程式说明和运行结论以仓库当前源码及本地验证为准。 diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/.nojekyll @@ -0,0 +1 @@ + diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..4748ec5 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,31 @@ + + +
+ + +404 / TRACE NOT FOUND
返回实验首页,运行一次 Save,观察 delegate、生命周期、target-action 与 closure 的真实顺序。
UIKit · Guided Learning · Observable Runtime
+这不是提醒事项业务 App,而是一台 UIKit 显微镜:学习者先预测,再操作真实界面,通过结构化日志与 Call Stack 分清生命周期、delegate、target-action 和 closure。
+A code-first UIKit learning lab that makes framework-driven behavior observable through guided experiments, structured logs, call stacks, and UI tests.
+ +
+ 一次编辑与 Save,穿过四种调用机制。
+ +didSelectItemAt列表把选择回调给业务控制器viewDidLoadUIKit 请求编辑页完成首次加载saveReminderSave 按钮触发注册的 actiononSave → snapshot业务 closure 回传并刷新列表Problem / 问题
+只读方法名,很容易把框架生命周期、delegate 回调、按钮 action 和业务 closure 混在一起。这个系统把一次学习拆成 Predict → Run → Observe → Verify → Recap,并把证据留在 App 内 Logs、Xcode Console、Call Stack 和 UI Test 中。
+The design problem is observability: learners need to distinguish framework callbacks from application-controlled data flow using evidence they can reproduce.
+GuidedStep 把操作、观察位置、预测问题、预期日志和胜利条件放在同一张训练卡里。
DemoLogStore 保存结构化事件并继续输出 Console;Log Panel 支持分类、搜索、关键事件与暂停滚动。
断点回答“谁触发”,UI Test 验证编辑回传与 Guide / Logs 入口,避免把文档里的预期当成运行事实。
+
+ ONE FLOW · FOUR MECHANISMS
+展示页只陈述能够从当前仓库或本地构建直接复核的事实。
+本地 iPhone 17 Pro Simulator:testEditFirstReminderTitle 9.456s;testOpenLogsAndGuide 27.483s。来源是当前 XCTest 文件与本次本地运行,不代表完整回归覆盖。
网页负责解释系统,运行证据仍来自 UIKit App。仓库默认使用 iPhone 17 Pro Simulator,也可通过 Make 参数切换设备。
+git clone https://github.com/estelledc/UIKitLifecycleDemo.git
+cd UIKitLifecycleDemo
+make build
+make run
+make test-ui
+ 这是一套学习实验室,不是生产级提醒事项 App。它不包含网络、持久化、EventKit 或真实业务状态;Guide 描述预期观察,不会伪造日志;两条 UI Test 只覆盖核心编辑流程和 Guide / Logs 入口,尚未覆盖所有高级实验。项目也不声称仅凭阅读或运行 Demo 就能掌握 UIKit。
+