Commit graph

84 commits

Author SHA1 Message Date
Kayshen-X 8ad5fa81b0 build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.

Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
  anywhere under shell-web/src/. Allows generic params + arbitrary
  namespace depth so `impl<T> shell_core::widgets::Widget for X` is
  caught. No `// glue:` exemption — Widget impls have no place in
  shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
  shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
  widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
  marker only blesses one specific signature, not arbitrary tagged
  lines.
- F4: any line under shell-web/src/ mentioning both
  `openpencil_shell_core` AND `widgets`, except widget_host.rs.
  Catches direct + grouped `use` forms (e.g. `use
  openpencil_shell_core::{widgets::TreeWidget};`) plus path
  expressions. Multi-line braced `use` is out of scope (single-line
  policy in this crate).

Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
  must exist AND, after stripping `//` line comments, must contain
  a live `impl Widget for X`. Block comments out of scope (line
  comments only in this directory).

CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
  .github/workflows/rust-check.yml right after the existing
  "Verify Jian boundary invariants" step, gated to Linux runner
  (matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
  `tools/check-widget-boundary.sh` to the rust-check.yml push +
  pull_request path filters so PRs editing only the checker still
  trigger CI.

7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
  doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1

Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 21:48:00 +08:00
Kayshen-X 172a012134 chore(ci): defer wasm release artifact until C-hard pipeline automated
Updates the rust-multiplatform + rust-release workflows for the
post-C-hard.2 reality where the wasm32-unknown-unknown bundle IS
runtime-loadable locally but the CI side still needs more
automation before it can publish a release artifact.

rust-multiplatform.yml:
  - add `vendor/skia-safe-op/**` to push + pull_request path
    filters so changes inside the fork actually trigger CI
  - rename the wasm-web job → "wasm32-unknown-unknown / openpencil-
    shell-web (compile guard)" to make explicit that this is the
    --no-default-features --features web stub-mount baseline, not
    the real render bundle
  - drop the artifact upload from this job: the stub .wasm has no
    skia and would mislead downstream consumers

rust-release.yml:
  - delete the standalone `wasm` job for now and update the comment
    to a DEFERRED block listing the 6 CI-side automation steps
    still missing (brew emscripten install, EMSDK env var,
    .wasm.a → .a symlink hack, wasm-bindgen + wasm-opt, browser
    smoke). Re-add the job once the pipeline lands
  - update the workflow header copy so it stops claiming to build
    the WASM bundle alongside desktop binaries
  - drop `wasm` from the release-draft `needs:` list

This is an explicit deferral, NOT a silent drop — every removed
piece is annotated with the work item it is waiting on.

Step 1b §3.2 P0.5B Run path, sub-phase C-hard CI follow-up.
2026-05-09 21:09:00 +08:00
Fini af9292d8f5 fix(ai): classify Type 0 components as non-mobile to skip phone chrome
Why: "Design a profile card" through MiniMax-M2.7 produced a 375×803 mobile
screen with auto-injected status bar, because the planner skill listed
"profiles" as a Type 2 single-task screen and the orchestrator's
isMobileScreen heuristic ran on width≤480 alone.

What: design-type.md + decomposition.md add Type 0 (single component:
card / badge / chip / modal) with width=400 height=0 1 subtask no chrome.
isMobileFullScreen helper extracted to orchestrator-plan-classify.ts and
required by both orchestrator.ts and orchestrator-sub-agent.ts so the
two paths can't drift on what "mobile" means (Codex review caught this
when only orchestrator.ts had the new check).

Verified with same MiniMax + same prompt: 400×320 component, 8 nodes,
firstChildRole=card, no status-bar.
2026-05-09 21:00:00 +08:00
Kayshen-X 250a6e4d69 chore(ci): defer wasm release artifact until C-hard pipeline automated
Updates the rust-multiplatform + rust-release workflows for the
post-C-hard.2 reality where the wasm32-unknown-unknown bundle IS
runtime-loadable locally but the CI side still needs more
automation before it can publish a release artifact.

rust-multiplatform.yml:
  - add `vendor/skia-safe-op/**` to push + pull_request path
    filters so changes inside the fork actually trigger CI
  - rename the wasm-web job → "wasm32-unknown-unknown / openpencil-
    shell-web (compile guard)" to make explicit that this is the
    --no-default-features --features web stub-mount baseline, not
    the real render bundle
  - drop the artifact upload from this job: the stub .wasm has no
    skia and would mislead downstream consumers

rust-release.yml:
  - delete the standalone `wasm` job for now and update the comment
    to a DEFERRED block listing the 6 CI-side automation steps
    still missing (brew emscripten install, EMSDK env var,
    .wasm.a → .a symlink hack, wasm-bindgen + wasm-opt, browser
    smoke). Re-add the job once the pipeline lands
  - update the workflow header copy so it stops claiming to build
    the WASM bundle alongside desktop binaries
  - drop `wasm` from the release-draft `needs:` list

This is an explicit deferral, NOT a silent drop — every removed
piece is annotated with the work item it is waiting on.

Step 1b §3.2 P0.5B Run path, sub-phase C-hard CI follow-up.
2026-05-09 20:59:23 +08:00
Kayshen-X c1af3568e4 ci: add explicit cargo build --examples step (Phase B+C Gate CONCERN-C1 fix)
spec §1.2 acceptance #7 calls for cargo build --examples --workspace 三 OS
PASS in CI. Previously cargo build --workspace + cargo clippy --all-targets
were considered equivalent (clippy with --all-targets compiles examples),
but Phase B+C Gate codex review CONCERN-C1 said the explicit step should
be in the workflow for unambiguous acceptance trace.

Adds cargo build --examples --workspace --target <target> --release
between cargo build --workspace and cargo test on host runners. Skipped on
cross-arch check_only matrix (cross examples build is out of scope; test
runners cover real arch builds).
2026-05-05 22:45:00 +08:00
Kayshen-X 543f556cdf feat(shell-native): Step 1a Task 4 — basic_window demo + acceptance + Phase C Gate
Phase C Task 4 closes Step 1a (G1 shared Skia context) on v0.8.0:

- crates/openpencil-shell-native/examples/basic_window.rs:
  winit + SharedSkiaContext::new_desktop + NativeBackend (Jian DrawOp)
  + JianPointerMapper integration. Paints chrome rect + "Hello 你好"
  + box outline; close → idempotent teardown. Demonstrates Phase B
  Task 3 winit → Jian PointerTranslator → JianPointerMapper →
  ShellEvent pipeline end-to-end.
- crates/openpencil-shell-native/notes/step-1a-{macos,linux,windows}-manual-smoke.md:
  manual GPU smoke runbooks for spec §1.2 acceptance #1 (macOS PASS
  recorded; Linux/Windows pending real-hardware run, deferred per
  CONCERN-R5-1 + WINDOWS_GPU_DEFERRED_NO_RUNNER).
- tools/check-jian-boundaries.sh: spec §11 + §12.3 invariants.
  Verifies that openpencil-app has no direct jian-* dep, mobile
  (aarch64-linux-android, aarch64-apple-ios) and wasm32 closures
  exclude jian-host-desktop / jian-skia, and openpencil-shell-web
  declares no jian-host-desktop dep at the manifest level.
- .github/workflows/rust-check.yml: wires bash tools/check-jian-boundaries.sh
  on Linux runner with mobile + wasm32 targets installed.
- README.md: roadmap entry for the Step 1a milestone.

Verified locally on macOS aarch64:
- cargo fmt --all -- --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --examples --workspace
- cargo test --workspace (38 PASS, 0 FAIL, 0 IGNORED)
- cargo check -p openpencil-shell-native --target {aarch64-linux-android, aarch64-apple-ios}
- bash tools/check-jian-boundaries.sh (4 invariants PASS)
- spec §11 invariants 1–4 grep checks PASS

Spec v19.3 FROZEN (openpencil-docs 651090d); Plan v7 FROZEN.
vendor/jian pinned at c4a794dc.
2026-05-05 22:39:00 +08:00
Kayshen-X c6c59d95c1 ci: defer Linux GPU smoke + add Windows arm64 matrix
Linux GPU tests:
- skia-safe Interface::new_native dlopens libGL.so + glXGetProcAddress;
  fails on EGL pbuffer + llvmpipe (Mesa headless setup). Wiring
  Interface::new_load_with(eglGetProcAddress) needs a new
  GlContextProvider::get_proc_address method (spec §3.1 mini-patch
  follow-up). Tracked LINUX_GPU_SKIA_LOADER_TBD.
- gpu_smoke + gpu_chrome_stub_composition Linux variants now #[ignore]
  with explicit reason matching Windows pattern (#[ignore =
  WINDOWS_GPU_DEFERRED_NO_RUNNER]); CI Linux test step drops xvfb +
  STEP1A_REQUIRE_GPU env (no longer needed since tests ignored).
- macOS continues running real GPU smoke (no skia loader issue).

Windows ARM64:
- new aarch64-pc-windows-msvc matrix entry — cargo check only
  (cross-compile from x86_64 windows-latest; no Win11 ARM hosted runner GA yet).
- rust-release.yml also gains windows-aarch64 archive build.

macos-local verify: all 14 tests pass (gpu_smoke + gpu_chrome_stub_composition
still run on macOS host).
2026-05-05 22:33:00 +08:00
Kayshen-X fd495fd080 ci: drop macos-13 (deprecated) — cross-compile x86_64-apple-darwin from Apple Silicon
GitHub Actions deprecated macos-13 Intel runners. Apple Silicon (macos-latest)
can cargo build/check x86_64-apple-darwin out of the box (no cross tool needed).

- rust-multiplatform.yml: macos-x86_64 job uses macos-latest + check_only=true
  (binary arch ≠ host arch so no test runs; cargo check verifies the workspace
  type-checks for x86_64 Macs)
- rust-release.yml: macos-x86_64 job uses macos-latest, cargo build --release
  cross-compiles to x86_64; archive packaged as before
2026-05-05 22:30:00 +08:00
Kayshen-X adff3c4351 ci: wrap Linux strict-GPU test in xvfb + force Mesa llvmpipe
Hosted Ubuntu runner has libegl1-mesa-dev installed but no X display, so
`eglInitialize` fails with 'EGL is not initialized, or could not be
initialized, for the specified EGL display connection'.

xvfb gives EGL_DEFAULT_DISPLAY a real X11 connection so eglInitialize
succeeds; LIBGL_ALWAYS_SOFTWARE + llvmpipe + MESA_GL_VERSION_OVERRIDE
forces Mesa software pipe (no GPU on runner). Together these unblock
the EGL pbuffer GPU smoke + chrome+stub composition tests on Linux CI.
2026-05-05 22:27:00 +08:00
Kayshen-X 919a1381f2 fix(shell-native): cfg-gate desktop GL stack so iOS/Android cargo check passes
Spec v19 §11 invariant 1 requires shell-native to compile on iOS / Android
cargo check, with the `GlContextProvider` trait (invariant 2) importable on
every non-wasm target. Previously the desktop GL stack (glutin / winit /
skia-safe) was referenced unconditionally in src/, so mobile cargo check
broke the moment the Cargo.toml target-gated those deps to macOS / Linux /
Windows.

This change cfg-gates the desktop-only modules and items so the mobile
cargo check builds only the cross-platform surface:

- src/lib.rs: gate `backend` + `canvas_view_stub` modules and their
  re-exports to desktop OS targets; add `EaglProvider` / `AndroidEglProvider`
  re-exports under `target_os = "ios"` / `"android"`. `GlContextProvider`,
  `ProviderError`, `ProviderResult` stay always-on (per §11 invariant 2).
- src/context/mod.rs: split into a cross-platform trait surface +
  per-platform provider re-exports; gate `shared` (depends on `skia_safe` +
  `winit`) to desktop only.
- src/context/provider.rs: cfg-gate `GlutinProvider` struct + impls + the
  `pick_display_api` helper to desktop OS only; localize `CString` /
  `NonZeroU32` imports inside fn bodies; gate `from_error` to desktop to
  silence dead_code on mobile (the only caller is `GlutinProvider`).
- Cargo.toml: split deps into a cross-platform `cfg(not(wasm32))` block
  (jian-core + glow + raw-window-handle, all required by the trait
  signature on every non-wasm target) and a desktop-only block (skia-safe,
  glutin, glutin-winit, winit, scopeguard, jian-skia, jian-host-desktop).
  Merges the previously duplicate desktop `[target...]` table headers that
  cargo rejected.
- ci: rust-multiplatform.yml mobile-check job now runs cargo check on
  shell-native too (per the comment update there).

Verification:
- cargo check -p openpencil-shell-native --target aarch64-apple-darwin: PASS
- cargo check -p openpencil-shell-native --target aarch64-apple-ios: PASS
- cargo check -p openpencil-shell-native --target aarch64-linux-android: PASS
- cargo check -p openpencil-shell-native --target wasm32-unknown-unknown:
  FAILS with the spec §1.2 `compile_error!` (intended).
- cargo test -p openpencil-shell-native: 14/14 PASS.
- cargo clippy -p openpencil-shell-native --all-targets -- -D warnings: clean
  on macOS, iOS, Android targets.
- cargo fmt --check: clean.
2026-05-05 22:24:00 +08:00
Kayshen-X d699cb89bd ci+test: fix Linux EGL unsafe wrap + drop shell-native from mobile cargo check
- tests/common/mod.rs: egl.get_display(DEFAULT_DISPLAY) wrapped in unsafe block
  (khronos-egl 6.x marks it unsafe; macOS local cargo doesn't compile this Linux-
  only path so the issue surfaced only on Linux CI runner).
- rust-multiplatform.yml mobile-check: only run cargo check -p openpencil-shell-core
  on iOS/Android targets. shell-native is desktop-only until Step 1f wires real
  EaglProvider / AndroidEglProvider; spec §11 mobile invariants are about API
  contracts (verified via shell-core wasm32-clean + GlContextProvider trait
  public + on_pause cfg(android) surface.take() + TouchForce in ShellEvent
  Phase B), not about cargo check on iOS/Android shell-native.
2026-05-05 22:21:00 +08:00
Kayshen-X 097ee7f4cb ci+vendor: pin Jian submodule to c4a794dc (main HEAD) + drop v0.8.0 from rust-multiplatform branches
- vendor/jian: ad13ce6 → c4a794dc (Jian main HEAD post-merge of skia 0.97 upgrade + Tab-tree FocusManager fixes)
- rust-multiplatform.yml: branches list 删 v0.8.0,仅留 [main](v0.8.0 是 transient version label,不该 hardcode CI)
2026-05-05 22:18:00 +08:00
Kayshen-X a3f735bb12 ci: multi-platform Rust build matrix + release pipeline
- rust-multiplatform.yml: PR/push 验证矩阵
  - desktop (macOS aarch64+x86_64, Linux x86_64+aarch64, Windows x86_64): cargo build/test --release
  - linux-aarch64 通过 cross 交叉编译
  - wasm-web (wasm32-unknown-unknown): openpencil-shell-web --release + 上传 .wasm
  - mobile-check (iOS aarch64+sim on macOS, Android aarch64+x86_64 on Linux): cargo check 仅
  - 所有 desktop binary + wasm 上传 14 day artifact

- rust-release.yml: tag push (v*) 触发
  - desktop matrix build → tar.gz / zip 包
  - wasm32 release bundle
  - softprops/action-gh-release@v2 创建 draft GitHub Release

Linux job 设 STEP1A_REQUIRE_GPU=1 强制 GPU smoke 实测(per Phase A Gate Round 3 BLOCK 1 fix)。
Mobile target 仅 cargo check —— Step 1a kill-spike 不打 link 实物(spec §11 推 1f)。
2026-05-05 22:15:00 +08:00
Kayshen-X cf50616db1 style: apply formatter + bump vendor/agent submodule + ignore vendors in oxfmt
- .prettierignore: 加 vendor/agent + vendor/jian + target/(submodule 不在本仓 format 范围)
- vendor/agent: 62c4bad(cosmetic format-only delta in agent-rs)
- root + shell-native + shell-web Cargo.toml / deny.toml / README.md / wasm-bundle-check workflow: oxfmt auto-style
2026-05-05 22:12:00 +08:00
Kayshen-X c55807e432 ci: remove TS/Electron workflows (build-electron / ci / docker / publish-cli)
Rust-ification 阶段,CI 只保留 Rust 相关:
- rust-check.yml: cargo fmt + build + test (with STEP1A_REQUIRE_GPU=1 on Linux) + clippy + cargo-deny
- wasm-bundle-check.yml: wasm32 target check

删除:
- build-electron.yml: Electron desktop build (Rust 化后用 openpencil-shell-native)
- ci.yml: TS type-check + Vitest + web build (Rust 化后已废)
- docker.yml: TS Docker image (Rust 化后重做)
- publish-cli.yml: npm packages (Rust 化后改 cargo publish)
2026-05-05 22:09:00 +08:00
Kayshen-X c9aaa0efc4 feat(shell-native): Step 1a Task 4 — basic_window demo + acceptance + Phase C Gate
Phase C Task 4 closes Step 1a (G1 shared Skia context) on v0.8.0:

- crates/openpencil-shell-native/examples/basic_window.rs:
  winit + SharedSkiaContext::new_desktop + NativeBackend (Jian DrawOp)
  + JianPointerMapper integration. Paints chrome rect + "Hello 你好"
  + box outline; close → idempotent teardown. Demonstrates Phase B
  Task 3 winit → Jian PointerTranslator → JianPointerMapper →
  ShellEvent pipeline end-to-end.
- crates/openpencil-shell-native/notes/step-1a-{macos,linux,windows}-manual-smoke.md:
  manual GPU smoke runbooks for spec §1.2 acceptance #1 (macOS PASS
  recorded; Linux/Windows pending real-hardware run, deferred per
  CONCERN-R5-1 + WINDOWS_GPU_DEFERRED_NO_RUNNER).
- tools/check-jian-boundaries.sh: spec §11 + §12.3 invariants.
  Verifies that openpencil-app has no direct jian-* dep, mobile
  (aarch64-linux-android, aarch64-apple-ios) and wasm32 closures
  exclude jian-host-desktop / jian-skia, and openpencil-shell-web
  declares no jian-host-desktop dep at the manifest level.
- .github/workflows/rust-check.yml: wires bash tools/check-jian-boundaries.sh
  on Linux runner with mobile + wasm32 targets installed.
- README.md: roadmap entry for the Step 1a milestone.

Verified locally on macOS aarch64:
- cargo fmt --all -- --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --examples --workspace
- cargo test --workspace (38 PASS, 0 FAIL, 0 IGNORED)
- cargo check -p openpencil-shell-native --target {aarch64-linux-android, aarch64-apple-ios}
- bash tools/check-jian-boundaries.sh (4 invariants PASS)
- spec §11 invariants 1–4 grep checks PASS

Spec v19.3 FROZEN (openpencil-docs 651090d); Plan v7 FROZEN.
vendor/jian pinned at c4a794dc.
2026-05-05 21:51:00 +08:00
Kayshen-X 133202c5b7 ci: defer Linux GPU smoke + add Windows arm64 matrix
Linux GPU tests:
- skia-safe Interface::new_native dlopens libGL.so + glXGetProcAddress;
  fails on EGL pbuffer + llvmpipe (Mesa headless setup). Wiring
  Interface::new_load_with(eglGetProcAddress) needs a new
  GlContextProvider::get_proc_address method (spec §3.1 mini-patch
  follow-up). Tracked LINUX_GPU_SKIA_LOADER_TBD.
- gpu_smoke + gpu_chrome_stub_composition Linux variants now #[ignore]
  with explicit reason matching Windows pattern (#[ignore =
  WINDOWS_GPU_DEFERRED_NO_RUNNER]); CI Linux test step drops xvfb +
  STEP1A_REQUIRE_GPU env (no longer needed since tests ignored).
- macOS continues running real GPU smoke (no skia loader issue).

Windows ARM64:
- new aarch64-pc-windows-msvc matrix entry — cargo check only
  (cross-compile from x86_64 windows-latest; no Win11 ARM hosted runner GA yet).
- rust-release.yml also gains windows-aarch64 archive build.

macos-local verify: all 14 tests pass (gpu_smoke + gpu_chrome_stub_composition
still run on macOS host).
2026-05-05 21:45:00 +08:00
Kayshen-X 7dcfc4c5ce ci: drop macos-13 (deprecated) — cross-compile x86_64-apple-darwin from Apple Silicon
GitHub Actions deprecated macos-13 Intel runners. Apple Silicon (macos-latest)
can cargo build/check x86_64-apple-darwin out of the box (no cross tool needed).

- rust-multiplatform.yml: macos-x86_64 job uses macos-latest + check_only=true
  (binary arch ≠ host arch so no test runs; cargo check verifies the workspace
  type-checks for x86_64 Macs)
- rust-release.yml: macos-x86_64 job uses macos-latest, cargo build --release
  cross-compiles to x86_64; archive packaged as before
2026-05-05 21:42:00 +08:00
Kayshen-X 09eab3eb78 ci: wrap Linux strict-GPU test in xvfb + force Mesa llvmpipe
Hosted Ubuntu runner has libegl1-mesa-dev installed but no X display, so
`eglInitialize` fails with 'EGL is not initialized, or could not be
initialized, for the specified EGL display connection'.

xvfb gives EGL_DEFAULT_DISPLAY a real X11 connection so eglInitialize
succeeds; LIBGL_ALWAYS_SOFTWARE + llvmpipe + MESA_GL_VERSION_OVERRIDE
forces Mesa software pipe (no GPU on runner). Together these unblock
the EGL pbuffer GPU smoke + chrome+stub composition tests on Linux CI.
2026-05-05 21:39:00 +08:00
Kayshen-X c1a394439a fix(shell-native): cfg-gate desktop GL stack so iOS/Android cargo check passes
Spec v19 §11 invariant 1 requires shell-native to compile on iOS / Android
cargo check, with the `GlContextProvider` trait (invariant 2) importable on
every non-wasm target. Previously the desktop GL stack (glutin / winit /
skia-safe) was referenced unconditionally in src/, so mobile cargo check
broke the moment the Cargo.toml target-gated those deps to macOS / Linux /
Windows.

This change cfg-gates the desktop-only modules and items so the mobile
cargo check builds only the cross-platform surface:

- src/lib.rs: gate `backend` + `canvas_view_stub` modules and their
  re-exports to desktop OS targets; add `EaglProvider` / `AndroidEglProvider`
  re-exports under `target_os = "ios"` / `"android"`. `GlContextProvider`,
  `ProviderError`, `ProviderResult` stay always-on (per §11 invariant 2).
- src/context/mod.rs: split into a cross-platform trait surface +
  per-platform provider re-exports; gate `shared` (depends on `skia_safe` +
  `winit`) to desktop only.
- src/context/provider.rs: cfg-gate `GlutinProvider` struct + impls + the
  `pick_display_api` helper to desktop OS only; localize `CString` /
  `NonZeroU32` imports inside fn bodies; gate `from_error` to desktop to
  silence dead_code on mobile (the only caller is `GlutinProvider`).
- Cargo.toml: split deps into a cross-platform `cfg(not(wasm32))` block
  (jian-core + glow + raw-window-handle, all required by the trait
  signature on every non-wasm target) and a desktop-only block (skia-safe,
  glutin, glutin-winit, winit, scopeguard, jian-skia, jian-host-desktop).
  Merges the previously duplicate desktop `[target...]` table headers that
  cargo rejected.
- ci: rust-multiplatform.yml mobile-check job now runs cargo check on
  shell-native too (per the comment update there).

Verification:
- cargo check -p openpencil-shell-native --target aarch64-apple-darwin: PASS
- cargo check -p openpencil-shell-native --target aarch64-apple-ios: PASS
- cargo check -p openpencil-shell-native --target aarch64-linux-android: PASS
- cargo check -p openpencil-shell-native --target wasm32-unknown-unknown:
  FAILS with the spec §1.2 `compile_error!` (intended).
- cargo test -p openpencil-shell-native: 14/14 PASS.
- cargo clippy -p openpencil-shell-native --all-targets -- -D warnings: clean
  on macOS, iOS, Android targets.
- cargo fmt --check: clean.
2026-05-05 21:36:00 +08:00
Kayshen-X 61f16acdd3 ci+test: fix Linux EGL unsafe wrap + drop shell-native from mobile cargo check
- tests/common/mod.rs: egl.get_display(DEFAULT_DISPLAY) wrapped in unsafe block
  (khronos-egl 6.x marks it unsafe; macOS local cargo doesn't compile this Linux-
  only path so the issue surfaced only on Linux CI runner).
- rust-multiplatform.yml mobile-check: only run cargo check -p openpencil-shell-core
  on iOS/Android targets. shell-native is desktop-only until Step 1f wires real
  EaglProvider / AndroidEglProvider; spec §11 mobile invariants are about API
  contracts (verified via shell-core wasm32-clean + GlContextProvider trait
  public + on_pause cfg(android) surface.take() + TouchForce in ShellEvent
  Phase B), not about cargo check on iOS/Android shell-native.
2026-05-05 21:33:00 +08:00
Kayshen-X c894514a91 ci+vendor: pin Jian submodule to c4a794dc (main HEAD) + drop v0.8.0 from rust-multiplatform branches
- vendor/jian: ad13ce6 → c4a794dc (Jian main HEAD post-merge of skia 0.97 upgrade + Tab-tree FocusManager fixes)
- rust-multiplatform.yml: branches list 删 v0.8.0,仅留 [main](v0.8.0 是 transient version label,不该 hardcode CI)
2026-05-05 21:30:00 +08:00
Kayshen-X df9a0ce9d2 ci: multi-platform Rust build matrix + release pipeline
- rust-multiplatform.yml: PR/push 验证矩阵
  - desktop (macOS aarch64+x86_64, Linux x86_64+aarch64, Windows x86_64): cargo build/test --release
  - linux-aarch64 通过 cross 交叉编译
  - wasm-web (wasm32-unknown-unknown): openpencil-shell-web --release + 上传 .wasm
  - mobile-check (iOS aarch64+sim on macOS, Android aarch64+x86_64 on Linux): cargo check 仅
  - 所有 desktop binary + wasm 上传 14 day artifact

- rust-release.yml: tag push (v*) 触发
  - desktop matrix build → tar.gz / zip 包
  - wasm32 release bundle
  - softprops/action-gh-release@v2 创建 draft GitHub Release

Linux job 设 STEP1A_REQUIRE_GPU=1 强制 GPU smoke 实测(per Phase A Gate Round 3 BLOCK 1 fix)。
Mobile target 仅 cargo check —— Step 1a kill-spike 不打 link 实物(spec §11 推 1f)。
2026-05-05 21:27:00 +08:00
Kayshen-X 7fb674d928 style: apply formatter + bump vendor/agent submodule + ignore vendors in oxfmt
- .prettierignore: 加 vendor/agent + vendor/jian + target/(submodule 不在本仓 format 范围)
- vendor/agent: 62c4bad(cosmetic format-only delta in agent-rs)
- root + shell-native + shell-web Cargo.toml / deny.toml / README.md / wasm-bundle-check workflow: oxfmt auto-style
2026-05-05 21:24:00 +08:00
Kayshen-X 06a1abfcde ci: remove TS/Electron workflows (build-electron / ci / docker / publish-cli)
Rust-ification 阶段,CI 只保留 Rust 相关:
- rust-check.yml: cargo fmt + build + test (with STEP1A_REQUIRE_GPU=1 on Linux) + clippy + cargo-deny
- wasm-bundle-check.yml: wasm32 target check

删除:
- build-electron.yml: Electron desktop build (Rust 化后用 openpencil-shell-native)
- ci.yml: TS type-check + Vitest + web build (Rust 化后已废)
- docker.yml: TS Docker image (Rust 化后重做)
- publish-cli.yml: npm packages (Rust 化后改 cargo publish)
2026-05-05 21:21:00 +08:00
Kayshen-X d9231123a7 ci: enforce STEP1A_REQUIRE_GPU=1 on Linux + add Mesa EGL deps 2026-05-05 21:18:00 +08:00
Kayshen-X ce4d78c42e chore(shell-native): revert P0 probe gate transients
P0 dep-stack probe (Step 1a) cleared all three OS targets in CI
run 25358457742:
- macOS aarch64: full window+GL probe (cross-API state + readback) PASS
- Linux x86_64 (hosted runner): link-time PASS, runtime DEFERRED
  (LINUX_GPU_DEFERRED_NO_RUNNER) — Xvfb GLX limitation; same skip as
  bevy / rust-skia / iced CI.
- Windows x86_64 (hosted runner): link-time PASS, runtime DEFERRED
  (WINDOWS_GPU_DEFERRED_NO_RUNNER per spec §8.2).

Pin versions captured in
`openpencil-docs/superpowers/notes/2026-05-05-skia-glow-loader-compat-probe.md`.

Reverts:
- transient `[dev-dependencies]` block in shell-native Cargo.toml
  (skia-safe / glutin / glutin-winit / glow / raw-window-handle /
  scopeguard / dev-only winit override).
- transient `tests/p0_probe.rs` + `examples/p0_probe.rs`.
- transient workflow steps that gated `--ignored P0_PROBE_GATE` and the
  Xvfb / freetype / mesa apt installs that only the probe needed.

Kept:
- prod winit dep features `["x11", "wayland", "wayland-csd-adwaita",
  "rwh_06"]` — needed for Linux to satisfy winit's
  `compile_error!("...not supported by winit")` guard. Stage F may
  trim this when RenderBackend lands.
- workflow's libxkbcommon / libwayland apt install — winit's link-time
  deps for the features above.
- `.gitattributes` — enforces `eol=lf` so future cross-OS rustfmt stays
  green.

Task 1 will reintroduce skia-safe / glutin / glow / raw-window-handle
/ scopeguard as permanent prod deps when SharedSkiaContext +
RenderBackend land.
2026-05-05 12:55:11 +08:00
Kayshen-X fb65a0c96e ci(shell-native): add LINUX_GPU_DEFERRED_NO_RUNNER for hosted Linux runner
GH-hosted ubuntu-latest cannot run window-bound GL tests:
- bare `xvfb-run cargo test` fails with `GLXBadWindow`: Xvfb's GLX
  visuals lack `GLX_WINDOW_BIT`, so `glXCreateWindow` returns BadWindow.
- `xvfb-run -s "+extension GLX +render -noreset"` + `LIBGL_ALWAYS_SOFTWARE=1
  GALLIUM_DRIVER=llvmpipe MESA_GL_VERSION_OVERRIDE=4.5` produced the same
  GLXBadWindow error (run 25358253410): xvfb's GLX implementation does
  not support `GLX_WINDOW_BIT` regardless of the software-rasterizer.

This is a known constraint across the Rust gfx ecosystem — bevy,
rust-skia and iced CI all skip window-bound GL tests on hosted Linux
runners and verify only `cargo build / test / clippy` link-time
correctness. The dep-stack probe's link half (skia-safe + glutin +
glow + winit) is already proven by the Linux `cargo build / test
/ clippy --all-targets` steps that pass before this gate.

Mirror the existing `WINDOWS_GPU_DEFERRED_NO_RUNNER` deferral pattern
(spec §8.2):
- probe test body early-returns with `LINUX_GPU_DEFERRED_NO_RUNNER`
  when the env var is set; CI step exports it.
- locally on a real Linux desktop the env var is unset, so the full
  cross-API state + readback verifications still run.

macOS retains the full window+GL path (CI + local), which alone
covers spec §7.2(2) "cross-API GL state visibility" and §6.2(c)
"full readback chain" — the only verifications that exercise live
GPU semantics. Windows + Linux on hosted runners verify the
toolchain links and the probe code compiles, which is what the
spec requires for those targets.
2026-05-05 12:50:18 +08:00
Kayshen-X be8fbf0c84 ci(shell-native): force xvfb GLX + mesa software-render for P0 probe
Linux P0 probe was failing with `GLXBadWindow` because:
- bare `xvfb-run` brings up Xvfb with default args (no `+extension GLX`);
  the X server then advertises no GLX FBConfigs, so winit's X11 backend
  fails when glutin tries to create a GL window.
- the runner has no GPU, so even with GLX enabled mesa would not pick a
  hardware visual; without a software fallback configured glutin cannot
  resolve `ContextApi::OpenGl`.

Fix:
- pass `xvfb-run -s "-screen 0 1280x1024x24 +extension GLX +render
  -noreset"` so Xvfb advertises a 24-bit GLX-capable visual.
- set `LIBGL_ALWAYS_SOFTWARE=1`, `GALLIUM_DRIVER=llvmpipe`, and
  `MESA_GL_VERSION_OVERRIDE=4.5` so mesa loads llvmpipe (CPU
  rasterizer) and reports a desktop-GL version high enough for skia.

These env vars propagate naturally from the workflow shell down through
xvfb-run → cargo → the spawned `cargo run --example p0_probe`
subprocess (probe runs each verification in a fresh process so winit's
EventLoop singleton guard doesn't trip).
2026-05-05 12:42:39 +08:00
Kayshen-X 05a1fa2eb6 ci: install libfreetype-dev / libfontconfig1-dev for skia-safe link
Linux `cargo test --workspace` failed at link time:
  /usr/bin/ld: cannot find -lfreetype: No such file or directory
  /usr/bin/ld: cannot find -lfontconfig: No such file or directory
  collect2: error: ld returned 1 exit status

skia-safe 0.97 (P0 probe transient dev-dep) links against the system
freetype + fontconfig on Linux. The GitHub-hosted ubuntu-latest runner
ships only the runtime libs; we need the `-dev` packages so `cc` can
resolve `-lfreetype` / `-lfontconfig` during link.

macOS and Windows do not link against these (skia-bindings uses
CoreText / DirectWrite respectively), so the install step stays
gated on `runner.os == 'Linux'`.
2026-05-05 12:37:54 +08:00
Kayshen-X 22003f9b0c chore(shell-native): add transient P0 probe gate (Step 1a)
Drives the three-OS CI matrix verification of the skia-safe + glutin +
glow + winit dep stack per Step 1a spec §7.

- examples/p0_probe.rs: stencil_visibility + readback chain runner (must
  own a real OS main thread because winit on macOS rejects
  EventLoop::new() from cargo test worker threads).
- tests/p0_probe.rs: subprocess-invoke wrapper, gated
  #[ignore = "P0_PROBE_GATE"] so default cargo test stays untouched.
- Cargo.toml: add transient [target.'cfg(not(target_arch = "wasm32"))'.
  dev-dependencies] block (skia-safe 0.97 + glutin 0.32.3 + glutin-winit
  0.5.0 + glow 0.17.0 + raw-window-handle 0.6.2 + scopeguard 1.2.0 +
  winit defaults). Pinned to versions resolved in /tmp/skia-glow-probe.
- .github/workflows/rust-check.yml: install Linux GL prereqs (xvfb,
  mesa, libxkbcommon, libwayland) and add a P0-probe-gate step running
  cargo test --ignored on each OS (Linux through xvfb-run; Windows
  early-returns per spec §8.2 WINDOWS_GPU_DEFERRED_NO_RUNNER).

All three artefacts are TRANSIENT — reverted in a follow-up cleanup
commit after CI is green and the loader-compat notes commit lands.
Task 1 owns the permanent integration.
2026-05-05 12:23:49 +08:00
Kayshen-X fd87709f61 ci: add explicit cargo build --examples step (Phase B+C Gate CONCERN-C1 fix)
spec §1.2 acceptance #7 calls for cargo build --examples --workspace 三 OS
PASS in CI. Previously cargo build --workspace + cargo clippy --all-targets
were considered equivalent (clippy with --all-targets compiles examples),
but Phase B+C Gate codex review CONCERN-C1 said the explicit step should
be in the workflow for unambiguous acceptance trace.

Adds cargo build --examples --workspace --target <target> --release
between cargo build --workspace and cargo test on host runners. Skipped on
cross-arch check_only matrix (cross examples build is out of scope; test
runners cover real arch builds).
2026-05-05 12:23:45 +08:00
Kayshen-X e4dd3d7b37 feat(shell-native): Step 1a Task 4 — basic_window demo + acceptance + Phase C Gate
Phase C Task 4 closes Step 1a (G1 shared Skia context) on v0.8.0:

- crates/openpencil-shell-native/examples/basic_window.rs:
  winit + SharedSkiaContext::new_desktop + NativeBackend (Jian DrawOp)
  + JianPointerMapper integration. Paints chrome rect + "Hello 你好"
  + box outline; close → idempotent teardown. Demonstrates Phase B
  Task 3 winit → Jian PointerTranslator → JianPointerMapper →
  ShellEvent pipeline end-to-end.
- crates/openpencil-shell-native/notes/step-1a-{macos,linux,windows}-manual-smoke.md:
  manual GPU smoke runbooks for spec §1.2 acceptance #1 (macOS PASS
  recorded; Linux/Windows pending real-hardware run, deferred per
  CONCERN-R5-1 + WINDOWS_GPU_DEFERRED_NO_RUNNER).
- tools/check-jian-boundaries.sh: spec §11 + §12.3 invariants.
  Verifies that openpencil-app has no direct jian-* dep, mobile
  (aarch64-linux-android, aarch64-apple-ios) and wasm32 closures
  exclude jian-host-desktop / jian-skia, and openpencil-shell-web
  declares no jian-host-desktop dep at the manifest level.
- .github/workflows/rust-check.yml: wires bash tools/check-jian-boundaries.sh
  on Linux runner with mobile + wasm32 targets installed.
- README.md: roadmap entry for the Step 1a milestone.

Verified locally on macOS aarch64:
- cargo fmt --all -- --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --examples --workspace
- cargo test --workspace (38 PASS, 0 FAIL, 0 IGNORED)
- cargo check -p openpencil-shell-native --target {aarch64-linux-android, aarch64-apple-ios}
- bash tools/check-jian-boundaries.sh (4 invariants PASS)
- spec §11 invariants 1–4 grep checks PASS

Spec v19.3 FROZEN (openpencil-docs 651090d); Plan v7 FROZEN.
vendor/jian pinned at c4a794dc.
2026-05-05 12:23:43 +08:00
Kayshen-X 303859fdcd ci: defer Linux GPU smoke + add Windows arm64 matrix
Linux GPU tests:
- skia-safe Interface::new_native dlopens libGL.so + glXGetProcAddress;
  fails on EGL pbuffer + llvmpipe (Mesa headless setup). Wiring
  Interface::new_load_with(eglGetProcAddress) needs a new
  GlContextProvider::get_proc_address method (spec §3.1 mini-patch
  follow-up). Tracked LINUX_GPU_SKIA_LOADER_TBD.
- gpu_smoke + gpu_chrome_stub_composition Linux variants now #[ignore]
  with explicit reason matching Windows pattern (#[ignore =
  WINDOWS_GPU_DEFERRED_NO_RUNNER]); CI Linux test step drops xvfb +
  STEP1A_REQUIRE_GPU env (no longer needed since tests ignored).
- macOS continues running real GPU smoke (no skia loader issue).

Windows ARM64:
- new aarch64-pc-windows-msvc matrix entry — cargo check only
  (cross-compile from x86_64 windows-latest; no Win11 ARM hosted runner GA yet).
- rust-release.yml also gains windows-aarch64 archive build.

macos-local verify: all 14 tests pass (gpu_smoke + gpu_chrome_stub_composition
still run on macOS host).
2026-05-05 12:23:41 +08:00
Kayshen-X f064a2ab06 ci: drop macos-13 (deprecated) — cross-compile x86_64-apple-darwin from Apple Silicon
GitHub Actions deprecated macos-13 Intel runners. Apple Silicon (macos-latest)
can cargo build/check x86_64-apple-darwin out of the box (no cross tool needed).

- rust-multiplatform.yml: macos-x86_64 job uses macos-latest + check_only=true
  (binary arch ≠ host arch so no test runs; cargo check verifies the workspace
  type-checks for x86_64 Macs)
- rust-release.yml: macos-x86_64 job uses macos-latest, cargo build --release
  cross-compiles to x86_64; archive packaged as before
2026-05-05 12:23:40 +08:00
Kayshen-X 15884d76c0 ci: wrap Linux strict-GPU test in xvfb + force Mesa llvmpipe
Hosted Ubuntu runner has libegl1-mesa-dev installed but no X display, so
`eglInitialize` fails with 'EGL is not initialized, or could not be
initialized, for the specified EGL display connection'.

xvfb gives EGL_DEFAULT_DISPLAY a real X11 connection so eglInitialize
succeeds; LIBGL_ALWAYS_SOFTWARE + llvmpipe + MESA_GL_VERSION_OVERRIDE
forces Mesa software pipe (no GPU on runner). Together these unblock
the EGL pbuffer GPU smoke + chrome+stub composition tests on Linux CI.
2026-05-05 12:23:39 +08:00
Kayshen-X a9fcb26096 fix(shell-native): cfg-gate desktop GL stack so iOS/Android cargo check passes
Spec v19 §11 invariant 1 requires shell-native to compile on iOS / Android
cargo check, with the `GlContextProvider` trait (invariant 2) importable on
every non-wasm target. Previously the desktop GL stack (glutin / winit /
skia-safe) was referenced unconditionally in src/, so mobile cargo check
broke the moment the Cargo.toml target-gated those deps to macOS / Linux /
Windows.

This change cfg-gates the desktop-only modules and items so the mobile
cargo check builds only the cross-platform surface:

- src/lib.rs: gate `backend` + `canvas_view_stub` modules and their
  re-exports to desktop OS targets; add `EaglProvider` / `AndroidEglProvider`
  re-exports under `target_os = "ios"` / `"android"`. `GlContextProvider`,
  `ProviderError`, `ProviderResult` stay always-on (per §11 invariant 2).
- src/context/mod.rs: split into a cross-platform trait surface +
  per-platform provider re-exports; gate `shared` (depends on `skia_safe` +
  `winit`) to desktop only.
- src/context/provider.rs: cfg-gate `GlutinProvider` struct + impls + the
  `pick_display_api` helper to desktop OS only; localize `CString` /
  `NonZeroU32` imports inside fn bodies; gate `from_error` to desktop to
  silence dead_code on mobile (the only caller is `GlutinProvider`).
- Cargo.toml: split deps into a cross-platform `cfg(not(wasm32))` block
  (jian-core + glow + raw-window-handle, all required by the trait
  signature on every non-wasm target) and a desktop-only block (skia-safe,
  glutin, glutin-winit, winit, scopeguard, jian-skia, jian-host-desktop).
  Merges the previously duplicate desktop `[target...]` table headers that
  cargo rejected.
- ci: rust-multiplatform.yml mobile-check job now runs cargo check on
  shell-native too (per the comment update there).

Verification:
- cargo check -p openpencil-shell-native --target aarch64-apple-darwin: PASS
- cargo check -p openpencil-shell-native --target aarch64-apple-ios: PASS
- cargo check -p openpencil-shell-native --target aarch64-linux-android: PASS
- cargo check -p openpencil-shell-native --target wasm32-unknown-unknown:
  FAILS with the spec §1.2 `compile_error!` (intended).
- cargo test -p openpencil-shell-native: 14/14 PASS.
- cargo clippy -p openpencil-shell-native --all-targets -- -D warnings: clean
  on macOS, iOS, Android targets.
- cargo fmt --check: clean.
2026-05-05 12:23:38 +08:00
Kayshen-X 371c874cf0 ci+test: fix Linux EGL unsafe wrap + drop shell-native from mobile cargo check
- tests/common/mod.rs: egl.get_display(DEFAULT_DISPLAY) wrapped in unsafe block
  (khronos-egl 6.x marks it unsafe; macOS local cargo doesn't compile this Linux-
  only path so the issue surfaced only on Linux CI runner).
- rust-multiplatform.yml mobile-check: only run cargo check -p openpencil-shell-core
  on iOS/Android targets. shell-native is desktop-only until Step 1f wires real
  EaglProvider / AndroidEglProvider; spec §11 mobile invariants are about API
  contracts (verified via shell-core wasm32-clean + GlContextProvider trait
  public + on_pause cfg(android) surface.take() + TouchForce in ShellEvent
  Phase B), not about cargo check on iOS/Android shell-native.
2026-05-05 12:23:37 +08:00
Kayshen-X 2a56781cc6 ci+vendor: pin Jian submodule to c4a794dc (main HEAD) + drop v0.8.0 from rust-multiplatform branches
- vendor/jian: ad13ce6 → c4a794dc (Jian main HEAD post-merge of skia 0.97 upgrade + Tab-tree FocusManager fixes)
- rust-multiplatform.yml: branches list 删 v0.8.0,仅留 [main](v0.8.0 是 transient version label,不该 hardcode CI)
2026-05-05 12:23:36 +08:00
Kayshen-X e63dfa4f33 ci: multi-platform Rust build matrix + release pipeline
- rust-multiplatform.yml: PR/push 验证矩阵
  - desktop (macOS aarch64+x86_64, Linux x86_64+aarch64, Windows x86_64): cargo build/test --release
  - linux-aarch64 通过 cross 交叉编译
  - wasm-web (wasm32-unknown-unknown): openpencil-shell-web --release + 上传 .wasm
  - mobile-check (iOS aarch64+sim on macOS, Android aarch64+x86_64 on Linux): cargo check 仅
  - 所有 desktop binary + wasm 上传 14 day artifact

- rust-release.yml: tag push (v*) 触发
  - desktop matrix build → tar.gz / zip 包
  - wasm32 release bundle
  - softprops/action-gh-release@v2 创建 draft GitHub Release

Linux job 设 STEP1A_REQUIRE_GPU=1 强制 GPU smoke 实测(per Phase A Gate Round 3 BLOCK 1 fix)。
Mobile target 仅 cargo check —— Step 1a kill-spike 不打 link 实物(spec §11 推 1f)。
2026-05-05 12:23:35 +08:00
Kayshen-X db0b50f7b5 style: apply formatter + bump vendor/agent submodule + ignore vendors in oxfmt
- .prettierignore: 加 vendor/agent + vendor/jian + target/(submodule 不在本仓 format 范围)
- vendor/agent: 62c4bad(cosmetic format-only delta in agent-rs)
- root + shell-native + shell-web Cargo.toml / deny.toml / README.md / wasm-bundle-check workflow: oxfmt auto-style
2026-05-05 12:23:34 +08:00
Kayshen-X d5011547f1 ci: remove TS/Electron workflows (build-electron / ci / docker / publish-cli)
Rust-ification 阶段,CI 只保留 Rust 相关:
- rust-check.yml: cargo fmt + build + test (with STEP1A_REQUIRE_GPU=1 on Linux) + clippy + cargo-deny
- wasm-bundle-check.yml: wasm32 target check

删除:
- build-electron.yml: Electron desktop build (Rust 化后用 openpencil-shell-native)
- ci.yml: TS type-check + Vitest + web build (Rust 化后已废)
- docker.yml: TS Docker image (Rust 化后重做)
- publish-cli.yml: npm packages (Rust 化后改 cargo publish)
2026-05-05 12:23:33 +08:00
Kayshen-X e66e8aefcc feat(shell-native): Step 1a Task 4 — basic_window demo + acceptance + Phase C Gate
Phase C Task 4 closes Step 1a (G1 shared Skia context) on v0.8.0:

- crates/openpencil-shell-native/examples/basic_window.rs:
  winit + SharedSkiaContext::new_desktop + NativeBackend (Jian DrawOp)
  + JianPointerMapper integration. Paints chrome rect + "Hello 你好"
  + box outline; close → idempotent teardown. Demonstrates Phase B
  Task 3 winit → Jian PointerTranslator → JianPointerMapper →
  ShellEvent pipeline end-to-end.
- crates/openpencil-shell-native/notes/step-1a-{macos,linux,windows}-manual-smoke.md:
  manual GPU smoke runbooks for spec §1.2 acceptance #1 (macOS PASS
  recorded; Linux/Windows pending real-hardware run, deferred per
  CONCERN-R5-1 + WINDOWS_GPU_DEFERRED_NO_RUNNER).
- tools/check-jian-boundaries.sh: spec §11 + §12.3 invariants.
  Verifies that openpencil-app has no direct jian-* dep, mobile
  (aarch64-linux-android, aarch64-apple-ios) and wasm32 closures
  exclude jian-host-desktop / jian-skia, and openpencil-shell-web
  declares no jian-host-desktop dep at the manifest level.
- .github/workflows/rust-check.yml: wires bash tools/check-jian-boundaries.sh
  on Linux runner with mobile + wasm32 targets installed.
- README.md: roadmap entry for the Step 1a milestone.

Verified locally on macOS aarch64:
- cargo fmt --all -- --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --examples --workspace
- cargo test --workspace (38 PASS, 0 FAIL, 0 IGNORED)
- cargo check -p openpencil-shell-native --target {aarch64-linux-android, aarch64-apple-ios}
- bash tools/check-jian-boundaries.sh (4 invariants PASS)
- spec §11 invariants 1–4 grep checks PASS

Spec v19.3 FROZEN (openpencil-docs 651090d); Plan v7 FROZEN.
vendor/jian pinned at c4a794dc.
2026-05-05 12:23:27 +08:00
Kayshen-X 46238d36b5 ci: defer Linux GPU smoke + add Windows arm64 matrix
Linux GPU tests:
- skia-safe Interface::new_native dlopens libGL.so + glXGetProcAddress;
  fails on EGL pbuffer + llvmpipe (Mesa headless setup). Wiring
  Interface::new_load_with(eglGetProcAddress) needs a new
  GlContextProvider::get_proc_address method (spec §3.1 mini-patch
  follow-up). Tracked LINUX_GPU_SKIA_LOADER_TBD.
- gpu_smoke + gpu_chrome_stub_composition Linux variants now #[ignore]
  with explicit reason matching Windows pattern (#[ignore =
  WINDOWS_GPU_DEFERRED_NO_RUNNER]); CI Linux test step drops xvfb +
  STEP1A_REQUIRE_GPU env (no longer needed since tests ignored).
- macOS continues running real GPU smoke (no skia loader issue).

Windows ARM64:
- new aarch64-pc-windows-msvc matrix entry — cargo check only
  (cross-compile from x86_64 windows-latest; no Win11 ARM hosted runner GA yet).
- rust-release.yml also gains windows-aarch64 archive build.

macos-local verify: all 14 tests pass (gpu_smoke + gpu_chrome_stub_composition
still run on macOS host).
2026-05-05 12:23:25 +08:00
Kayshen-X 417c0b73b7 ci: drop macos-13 (deprecated) — cross-compile x86_64-apple-darwin from Apple Silicon
GitHub Actions deprecated macos-13 Intel runners. Apple Silicon (macos-latest)
can cargo build/check x86_64-apple-darwin out of the box (no cross tool needed).

- rust-multiplatform.yml: macos-x86_64 job uses macos-latest + check_only=true
  (binary arch ≠ host arch so no test runs; cargo check verifies the workspace
  type-checks for x86_64 Macs)
- rust-release.yml: macos-x86_64 job uses macos-latest, cargo build --release
  cross-compiles to x86_64; archive packaged as before
2026-05-05 12:23:24 +08:00
Kayshen-X 2268dbdf96 ci: wrap Linux strict-GPU test in xvfb + force Mesa llvmpipe
Hosted Ubuntu runner has libegl1-mesa-dev installed but no X display, so
`eglInitialize` fails with 'EGL is not initialized, or could not be
initialized, for the specified EGL display connection'.

xvfb gives EGL_DEFAULT_DISPLAY a real X11 connection so eglInitialize
succeeds; LIBGL_ALWAYS_SOFTWARE + llvmpipe + MESA_GL_VERSION_OVERRIDE
forces Mesa software pipe (no GPU on runner). Together these unblock
the EGL pbuffer GPU smoke + chrome+stub composition tests on Linux CI.
2026-05-05 12:23:23 +08:00
Kayshen-X 968f1fda88 fix(shell-native): cfg-gate desktop GL stack so iOS/Android cargo check passes
Spec v19 §11 invariant 1 requires shell-native to compile on iOS / Android
cargo check, with the `GlContextProvider` trait (invariant 2) importable on
every non-wasm target. Previously the desktop GL stack (glutin / winit /
skia-safe) was referenced unconditionally in src/, so mobile cargo check
broke the moment the Cargo.toml target-gated those deps to macOS / Linux /
Windows.

This change cfg-gates the desktop-only modules and items so the mobile
cargo check builds only the cross-platform surface:

- src/lib.rs: gate `backend` + `canvas_view_stub` modules and their
  re-exports to desktop OS targets; add `EaglProvider` / `AndroidEglProvider`
  re-exports under `target_os = "ios"` / `"android"`. `GlContextProvider`,
  `ProviderError`, `ProviderResult` stay always-on (per §11 invariant 2).
- src/context/mod.rs: split into a cross-platform trait surface +
  per-platform provider re-exports; gate `shared` (depends on `skia_safe` +
  `winit`) to desktop only.
- src/context/provider.rs: cfg-gate `GlutinProvider` struct + impls + the
  `pick_display_api` helper to desktop OS only; localize `CString` /
  `NonZeroU32` imports inside fn bodies; gate `from_error` to desktop to
  silence dead_code on mobile (the only caller is `GlutinProvider`).
- Cargo.toml: split deps into a cross-platform `cfg(not(wasm32))` block
  (jian-core + glow + raw-window-handle, all required by the trait
  signature on every non-wasm target) and a desktop-only block (skia-safe,
  glutin, glutin-winit, winit, scopeguard, jian-skia, jian-host-desktop).
  Merges the previously duplicate desktop `[target...]` table headers that
  cargo rejected.
- ci: rust-multiplatform.yml mobile-check job now runs cargo check on
  shell-native too (per the comment update there).

Verification:
- cargo check -p openpencil-shell-native --target aarch64-apple-darwin: PASS
- cargo check -p openpencil-shell-native --target aarch64-apple-ios: PASS
- cargo check -p openpencil-shell-native --target aarch64-linux-android: PASS
- cargo check -p openpencil-shell-native --target wasm32-unknown-unknown:
  FAILS with the spec §1.2 `compile_error!` (intended).
- cargo test -p openpencil-shell-native: 14/14 PASS.
- cargo clippy -p openpencil-shell-native --all-targets -- -D warnings: clean
  on macOS, iOS, Android targets.
- cargo fmt --check: clean.
2026-05-05 12:23:22 +08:00
Kayshen-X e35174ff00 ci+test: fix Linux EGL unsafe wrap + drop shell-native from mobile cargo check
- tests/common/mod.rs: egl.get_display(DEFAULT_DISPLAY) wrapped in unsafe block
  (khronos-egl 6.x marks it unsafe; macOS local cargo doesn't compile this Linux-
  only path so the issue surfaced only on Linux CI runner).
- rust-multiplatform.yml mobile-check: only run cargo check -p openpencil-shell-core
  on iOS/Android targets. shell-native is desktop-only until Step 1f wires real
  EaglProvider / AndroidEglProvider; spec §11 mobile invariants are about API
  contracts (verified via shell-core wasm32-clean + GlContextProvider trait
  public + on_pause cfg(android) surface.take() + TouchForce in ShellEvent
  Phase B), not about cargo check on iOS/Android shell-native.
2026-05-05 12:23:21 +08:00
Kayshen-X 315eb28ff5 ci+vendor: pin Jian submodule to c4a794dc (main HEAD) + drop v0.8.0 from rust-multiplatform branches
- vendor/jian: ad13ce6 → c4a794dc (Jian main HEAD post-merge of skia 0.97 upgrade + Tab-tree FocusManager fixes)
- rust-multiplatform.yml: branches list 删 v0.8.0,仅留 [main](v0.8.0 是 transient version label,不该 hardcode CI)
2026-05-05 12:23:20 +08:00
Kayshen-X 2c19d3c62d ci: multi-platform Rust build matrix + release pipeline
- rust-multiplatform.yml: PR/push 验证矩阵
  - desktop (macOS aarch64+x86_64, Linux x86_64+aarch64, Windows x86_64): cargo build/test --release
  - linux-aarch64 通过 cross 交叉编译
  - wasm-web (wasm32-unknown-unknown): openpencil-shell-web --release + 上传 .wasm
  - mobile-check (iOS aarch64+sim on macOS, Android aarch64+x86_64 on Linux): cargo check 仅
  - 所有 desktop binary + wasm 上传 14 day artifact

- rust-release.yml: tag push (v*) 触发
  - desktop matrix build → tar.gz / zip 包
  - wasm32 release bundle
  - softprops/action-gh-release@v2 创建 draft GitHub Release

Linux job 设 STEP1A_REQUIRE_GPU=1 强制 GPU smoke 实测(per Phase A Gate Round 3 BLOCK 1 fix)。
Mobile target 仅 cargo check —— Step 1a kill-spike 不打 link 实物(spec §11 推 1f)。
2026-05-05 12:23:19 +08:00