feat(workspace): pin Jian submodule and shell wrapper deps (Step 1a Task 1)

Anchor v19 pivot at the workspace level: vendor Jian as a git submodule
pinned to fork commit ad13ce6 (P0.5 mini-gate GO; skia-safe 0.78 → 0.97 +
new pub draw_on_canvas adapter), wire jian-core / jian-skia / jian-host-desktop
as path deps with explicit version per spec §12.2, and re-export the
Jian render/geometry/scene types from shell-core so shell-native can
translate the OP RenderBackend facade into jian DrawOp commands.

shell-core stays wasm32-clean: only jian-core (already wasm32-validated
in P0.5) plus glam / bitflags / thiserror / tracing land here.
shell-native picks up the full P0-pinned GL stack (skia-safe 0.97.0,
glutin 0.32.3, glutin-winit 0.5.0, glow 0.17.0, winit 0.30.13,
raw-window-handle 0.6.2, scopeguard 1.2) plus jian-skia (textlayout)
and target-gated jian-host-desktop (default-features = false, no `run`
feature so we skip Jian's softbuffer raster present path — OP owns its
own GPU swap_buffers per spec §3.6).

Adds OP RenderBackend trait + Rect / Color (with RED/GREEN/BLUE/BLACK/
WHITE/TRANSPARENT named constants per spec §5.2) + TextLayout facade
that wraps jian_core::render::TextRun explicitly (TextRun has no Default
impl, fields enumerated to honour spec §5.2 round-2 CONCERN-1 fix).

Boundary checks all pass:
- wasm32 shell-web metadata: no jian-host-desktop / jian-skia
- aarch64-linux-android shell-native metadata: no jian-host-desktop
- shell-core src: no glutin / skia_safe / winit / glow imports

Tasks 2-4 (SharedSkiaContext + NativeBackend + ShellEvent mapping +
acceptance) follow per plan v7.
This commit is contained in:
Kayshen-X 2026-05-05 12:23:10 +08:00
parent ae13dc9bef
commit 2dcc8a96d3
12 changed files with 1117 additions and 301 deletions

3
.gitmodules vendored
View file

@ -4,3 +4,6 @@
[submodule "vendor/agent"]
path = vendor/agent
url = https://github.com/ZSeven-W/agent-rs.git
[submodule "vendor/jian"]
path = vendor/jian
url = git@github.com:Kayshen-X/jian.git

970
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ resolver = "2"
members = ["crates/*"]
exclude = [
"vendor/agent",
"vendor/jian",
"node_modules",
]

View file

@ -4,7 +4,7 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "OpenPencil shell — retained-mode widget tree, RenderBackend trait, parley + taffy (kickoff spec §1.2)"
description = "OpenPencil shell — platform-agnostic widget facade + RenderBackend trait, Jian re-export wrapper (spec v19 §1.2 / §5.2)"
[lib]
name = "openpencil_shell_core"
@ -12,11 +12,18 @@ path = "src/lib.rs"
[dependencies]
serde = { workspace = true, optional = true }
thiserror = { workspace = true }
tracing = { workspace = true }
# Pinned per Task 0.4 wasm32 feature matrix verdict
# (notes/2026-05-02-parley-wasm-feature-matrix.md "## 结论" 段)
parley = { version = "0.2", default-features = false, features = ["std"] }
taffy = { version = "0.6", default-features = false, features = ["std", "taffy_tree", "flexbox", "grid", "block_layout", "content_size"] }
# v19 pivot: glam Vec2 用作 Point2Dbitflags 用于 widget facade 内部 flag。
glam = { version = "0.29", default-features = false, features = ["std"] }
bitflags = "2"
# v19 pivot: re-export Jian render/geometry/scene types (spec §5.2 widget-facing facade
# wraps jian-core; native-only deps live in shell-native, jian-core itself is wasm32-clean).
# 双写 path + version per spec §12.2 ("path dep with explicit version") —— 防 cargo
# 在不同 workspace context 下因 version 不一致退化到错版本。
jian-core = { path = "../../vendor/jian/crates/jian-core", version = "0.0.1" }
[features]
default = []

View file

@ -0,0 +1,41 @@
//! Jian re-export module (spec v19 §2 / §1.2).
//!
//! `openpencil-shell-core` 是 Jian 薄 wrapper —— 把
//! `jian_core::render::{DrawOp, Paint, TextRun, …}` 经 OP 层 re-export 给
//! shell-native 内部翻译用。geometry / scene 类型加 `Jian*` 前缀,避免
//! 与 OP 自有 `Rect` / `Color` (`render_backend.rs`) 冲突。
//!
//! **契约spec §5.2.1 §746 行)**v19 wrapper 在 NativeBackend impl 内
//! **不让 widget code 直接看到 `jian_core::render::DrawOp`** —— widget 只
//! 调 OP `RenderBackend` method。这些 re-export 是给 **shell-native 内部**
//! 翻译用,不是给 widget code 用。
// ────────────────────────────────────────────────────────────────────────────
// jian_core::render —— DrawOp 命令缓冲 + Paint / TextRun / 其它绘图描述符
// ────────────────────────────────────────────────────────────────────────────
pub use jian_core::render::{
BorderRadii, DrawOp, GradientStop, ImageSource, LinearGradient, Paint, PathCommand,
RadialGradient, RenderCommand, ShadowSpec, StrokeOp, TextAlign, TextRun,
};
// ────────────────────────────────────────────────────────────────────────────
// jian_core::geometry —— 加 Jian* 前缀避免与 OP `Rect` 冲突
// ────────────────────────────────────────────────────────────────────────────
/// Jian 的 axis-aligned 矩形 (`euclid::Rect<f32>`)。
/// OP 自有 `crate::render_backend::Rect` 是 widget facade 用shell-native
/// 内部翻译时把 OP `Rect` → `JianRect``origin/size` → `euclid::Rect::new`)。
pub type JianRect = jian_core::geometry::Rect;
pub type Size = jian_core::geometry::Size;
pub type JianPoint = jian_core::geometry::Point;
pub type Affine2 = jian_core::geometry::Affine2;
// ────────────────────────────────────────────────────────────────────────────
// jian_core::scene —— Color (packed u32) 加 Jian* 前缀
// ────────────────────────────────────────────────────────────────────────────
/// Jian 的 packed-RGBA 颜色 (`pub struct Color(pub u32)`);与 OP
/// `crate::render_backend::Color` (RGBA f32 quad) 不同。NativeBackend 内部
/// 翻译 OP Color → JianColor 时用位打包。
pub type JianColor = jian_core::scene::Color;

View file

@ -1,20 +1,18 @@
//! OpenPencil shell core — platform-agnostic widget tree + render-backend trait.
//! OpenPencil shell core — platform-agnostic widget facade + RenderBackend trait.
//!
//! Per kickoff spec §1.2 (FROZEN 2026-05-02): this crate must compile on
//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate must compile on
//! wasm32-unknown-unknown. winit / accesskit_winit / skia-safe live in
//! `openpencil-shell-native`; wasm-bindgen / web-sys / CanvasKit live in
//! `openpencil-shell-web`.
//!
//! v19 pivot — 该 crate 是 Jian 薄 wrapper
//! - [`jian`] 模块 re-export `jian_core::render::{DrawOp, Paint, TextRun, …}`
//! + geometry/scene aliases给 shell-native 内部翻译用widget code 看不到)。
//! - [`render_backend`] 模块定义 OP 自有 widget-facing facade
//! (`RenderBackend` trait + `Rect` / `Color` / `TextLayout`spec §5.2)。
pub fn placeholder() -> &'static str {
"openpencil-shell-core skeleton"
}
pub mod jian;
pub mod render_backend;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn skeleton_returns_string() {
assert_eq!(placeholder(), "openpencil-shell-core skeleton");
}
}
// Re-export 主要 API 给上层 crate / widgets / tests 用。
pub use render_backend::{Color, Point2D, Rect, RenderBackend, TextLayout};

View file

@ -0,0 +1,181 @@
//! OP `RenderBackend` widget-facing facade (spec v19 §5.2).
//!
//! 这是 OP 的设计契约method-style API`fill_rect / stroke_rect / draw_text /
//! clip_rect / save / restore / translate / resize / dpi_scale`),与 Jian
//! `jian_core::render::RenderBackend`command-buffer style`new_surface /
//! begin_frame / draw(&DrawOp)` etc.)不直接重合。
//!
//! 实现路径 (per §5.2.1)
//! - `NativeBackend` (shell-native, Step 1a 起 frame-scoped 设计)**不**直接
//! impl 该 trait而是 expose 同名 method 但带 `canvas: &skia_safe::Canvas`
//! 显式参数。Step 1a basic_window demo 通过 `SharedSkiaContext::with_frame`
//! 闭包内调 NativeBackend method。Step 1c+ 真接入 widget tree 时再考虑用
//! `WithCanvas<'a>` newtype 把 canvas 注入 trait impl。
//! - `WebCanvasKitBackend` (shell-web, Step 1b):内部用 CanvasKit JS binding。
//! - `MobileBackend` (Step 1f):内部用 Metal / Vulkan / OpenGL ES。
//!
//! 该模块**不**引 skia-safe / Canvas / GL 类型 —— shell-core 必须 wasm32-clean
//! (per spec §1.2 boundary)。
use jian_core::render::{TextAlign, TextRun};
/// 2D 坐标点spec §5.2 钉死 `glam::Vec2`)。
pub type Point2D = glam::Vec2;
/// 矩形origin + size 双 Vec2 表示)。
#[derive(Debug, Clone, Copy)]
pub struct Rect {
pub origin: Point2D,
pub size: Point2D,
}
/// RGBA colorwidget facade 层;所有分量 0.0..=1.0)。
///
/// 命名常量定义在 OP 这层spec v19 round 5 CONCERN-R5-3 fix
/// `jian_core::scene::Color` 是 `Color(pub u32)` packed RGBA没有
/// RED/BLACK/etc 命名常量,调用方用 `JianColor::rgb(...)` 显式构造。
#[derive(Debug, Clone, Copy)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const RED: Self = Self {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const GREEN: Self = Self {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
pub const BLUE: Self = Self {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
pub const BLACK: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const WHITE: Self = Self {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const TRANSPARENT: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
}
/// OP 的 TextLayout —— v19 删 parley 后定义在 OP 这层;薄薄包装
/// `jian_core::render::TextRun`。
///
/// 不持 layout context / glyph cacheshell-core wasm32-clean不能引
/// skia/parley/icu真 layout 在 NativeBackend::draw_text 时通过
/// `jian_skia::SkiaBackend` 的 textlayout featureskia textlayout,
/// ICU+harfbuzz做 shape + line break。
///
/// Step 1a TextLayout 仅作 "已 shape 的 TextRun 集合" 占位;
/// caret/selection/bidi/wrap 推 Step 1c+。signatures 钉死,避免后续 Step
/// API break。
#[derive(Debug, Clone)]
pub struct TextLayout {
runs: Vec<TextRun>,
}
impl TextLayout {
/// 单 run 构造Step 1a 唯一活跃路径Phase B/C demo + raster_text_smoke 用)。
///
/// 字段对齐 `jian_core::render::TextRun` (`vendor/jian/crates/jian-core/src/render/paint.rs:77-94`)
/// **TextRun 没有 `Default` impl**,必须显式构造所有字段。
/// - `content` / `font_family` / `font_size` / `color` / `origin`:调用方传入
/// - `font_weight: 400` (CSS Normal)
/// - `max_width: 0.0`"unknown; render at origin with no alignment adjustment"
/// - `align: TextAlign::Start`
/// - `line_height: 0.0`"default"
pub fn single_run(
content: &str,
font_family: &str,
font_size: f32,
color: jian_core::scene::Color,
origin: Point2D,
) -> Self {
let run = TextRun {
content: content.to_string(),
font_family: font_family.to_string(),
font_size,
font_weight: 400,
color,
origin: jian_core::geometry::Point::new(origin.x, origin.y),
max_width: 0.0,
align: TextAlign::Start,
line_height: 0.0,
};
Self { runs: vec![run] }
}
/// 已 shape 的 TextRun 集合视图。
pub fn runs(&self) -> &[TextRun] {
&self.runs
}
/// 平移所有 run 的 originNativeBackend::draw_text 在 widget origin
/// 基础上做平移)。返回新 layout原 layout 不动。
pub fn translated(&self, offset: Point2D) -> Self {
let runs = self
.runs
.iter()
.map(|r| {
let mut r2 = r.clone();
r2.origin =
jian_core::geometry::Point::new(r.origin.x + offset.x, r.origin.y + offset.y);
r2
})
.collect();
Self { runs }
}
}
/// Backend 抽象widget-facing facadespec §5.2)。
///
/// 注trait 不加 `Send` bound —— skia-safe 类型 `!Send`rust-skia
/// thread-boundBackend 在 render thread 内单独使用,无跨线程需求。
///
/// `NativeBackend` (shell-native) 在 1a **不**直接 impl 该 traitv19 round 3
/// BLOCK-R3-3 fix —— frame-scoped 设计避免跨帧 borrow而是 expose 同名
/// method 但带 `canvas: &skia_safe::Canvas` 显式参数。trait 签名内不出现任何
/// Skia / GPU-backend 特定类型。
pub trait RenderBackend {
/// Begin 帧backend 内部维护 current frame state不暴露 canvas 类型)。
fn begin_frame(&mut self);
fn end_frame(&mut self);
// 绘图 primitives —— widgets 调这些,不直接操作 canvas。
fn fill_rect(&mut self, rect: Rect, color: Color);
fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32);
fn draw_text(&mut self, layout: &TextLayout, origin: Point2D);
fn clip_rect(&mut self, rect: Rect);
// 变换栈。
fn save(&mut self);
fn restore(&mut self);
fn translate(&mut self, offset: Point2D);
// viewport / dpi。
fn resize(&mut self, width: u32, height: u32);
fn dpi_scale(&self) -> f32;
}

View file

@ -0,0 +1,87 @@
//! Task 1 Step 20-21: prove the Jian re-export wrapper compiles & is usable.
//!
//! Two anchor invariants for spec v19 §2 / §5.2
//! 1. `DrawOp::Rect` constructible **through OP re-export path**
//! (`openpencil_shell_core::jian::DrawOp` —— shell-native 内部翻译用)。
//! 2. `TextLayout::single_run` 产生**恰好一个** `TextRun`spec §5.2
//! 确认 `..Default::default()` 替代成显式字段后语义等价)。
use openpencil_shell_core::jian::{DrawOp, JianRect, Paint};
use openpencil_shell_core::render_backend::{Color, Point2D, TextLayout};
#[test]
fn drawop_rect_constructible_via_re_export() {
// 通过 OP re-export 路径构造 Jian DrawOp::Rect —— 证明 shell-core 的
// jian 模块把 jian_core::render::{DrawOp, Paint} 暴露出来了。
let rect = JianRect::new(
jian_core::geometry::Point::new(0.0, 0.0),
jian_core::geometry::Size::new(100.0, 50.0),
);
let paint = Paint::solid(jian_core::scene::Color::rgb(255, 0, 0));
let op = DrawOp::Rect { rect, paint };
match op {
DrawOp::Rect { rect, .. } => {
assert_eq!(rect.size.width, 100.0);
assert_eq!(rect.size.height, 50.0);
}
_ => panic!("expected DrawOp::Rect"),
}
}
#[test]
fn text_layout_single_run_creates_one_run() {
// 显式字段构造TextRun 没 Default impl—— 证明 spec §5.2 的
// single_run 路径产生恰好一个 runcontent/font_family/font_size
// 都按调用方传入设置。
let layout = TextLayout::single_run(
"Hello",
"system-ui",
16.0,
jian_core::scene::Color::rgb(0, 0, 0),
Point2D::new(10.0, 20.0),
);
assert_eq!(layout.runs().len(), 1);
let run = &layout.runs()[0];
assert_eq!(run.content, "Hello");
assert_eq!(run.font_family, "system-ui");
assert_eq!(run.font_size, 16.0);
assert_eq!(run.font_weight, 400);
assert_eq!(run.origin.x, 10.0);
assert_eq!(run.origin.y, 20.0);
assert_eq!(run.max_width, 0.0);
assert_eq!(run.line_height, 0.0);
}
#[test]
fn text_layout_translated_offsets_origin() {
// translated() 把 offset 加到每 run origin 上;原 layout 不变。
let layout = TextLayout::single_run(
"World",
"system-ui",
14.0,
jian_core::scene::Color::rgb(0, 0, 0),
Point2D::new(5.0, 10.0),
);
let shifted = layout.translated(Point2D::new(100.0, 200.0));
assert_eq!(shifted.runs()[0].origin.x, 105.0);
assert_eq!(shifted.runs()[0].origin.y, 210.0);
// 原 layout 不动
assert_eq!(layout.runs()[0].origin.x, 5.0);
assert_eq!(layout.runs()[0].origin.y, 10.0);
}
#[test]
fn op_color_constants_distinct() {
// spec §5.2 命名常量 6 全 —— RED/GREEN/BLUE/BLACK/WHITE/TRANSPARENT。
assert_eq!(Color::RED.r, 1.0);
assert_eq!(Color::GREEN.g, 1.0);
assert_eq!(Color::BLUE.b, 1.0);
assert_eq!(Color::BLACK.r, 0.0);
assert_eq!(Color::WHITE.r, 1.0);
assert_eq!(Color::TRANSPARENT.a, 0.0);
}

View file

@ -4,7 +4,7 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "OpenPencil shell — native (winit + skia-safe + accesskit) backend"
description = "OpenPencil shell — native (winit + skia-safe + accesskit) backend, integrating jian-skia + jian-host-desktop sub-pieces (spec v19 §3 / §5.2.1)"
[lib]
name = "openpencil_shell_native"
@ -17,17 +17,37 @@ openpencil-shell-core = { path = "../openpencil-shell-core", version = "0.1.0" }
# 如果不 cfg-gatecargo 会先 fetch + run skia-safe 的 build.rs在 wasm32 上构建失败),
# Step 5 grep `must NOT be compiled for wasm32` 永远命中不到——会被 skia 错误盖过。
#
# Phase 1 skeleton 阶段:故意保持 deps 最小,仅声明意图所需的核心 cratewinit
# skia-safe / accesskit / accesskit_winit 等在 Stage F 真正实现 RenderBackend 时再加。
# 原因Rust 1.80 toolchain (rust-toolchain.toml) 对 home/hashbrown 等 crate 当前
# 版本的 edition2024 / MSRV 1.81+ 需求不兼容,加完整依赖会导致下载阶段失败。
# 详见提交说明 + Phase 1 Gate codex review。
# v19 pivot (Task 1): 加 P0-pinned GL stack + jian-skia + jian-host-desktop。
# - P0 dep-stack pin 来自 P0 probeplan v7 §Task P0skia-safe 0.97 + glow 0.17 +
# glutin 0.32.3 + glutin-winit 0.5.0 + winit 0.30.13 + raw-window-handle 0.6.2 +
# scopeguard 1.2 全在 macOS/Windows/Linux GO
# - jian-skia (textlayout feature) 提供 SkiaBackend impl + skia textlayout替代 parley
# - jian-host-desktop (target-gate desktop only, 不含 `run` feature) 复用 PointerTranslator /
# scene::collect_draws_with_state / DesktopHost config helpersOP 自管 GPU event loop
# 不调 jian_host_desktop::runsoftbuffer raster present 不需要)。
#
# winit features 说明on Linux 必须显式开 `x11` 和/或 `wayland`,否则
# `platform_impl/mod.rs` 触发 `compile_error!("The platform you're compiling for is
# not supported by winit")`. macOS / Windows 各自的 backend 通过 cfg(target_os)
# 默认启用,不需要 feature flag。Step 1a 只在三大桌面 OS 跑 CI所以同时打开
# x11 + wayland 两个 Linux backend 就够。Stage F 真正接 RenderBackend 时会
# 重新审视(可能加 `serde` / `rwh_06` 等)。
# `platform_impl/mod.rs` 触发 `compile_error!`. macOS / Windows 各自的 backend 通过
# cfg(target_os) 默认启用,不需要 feature flag。Step 1a 三大桌面 OS CI 都跑,
# 所以同时打开 x11 + wayland 两个 Linux backend 就够。
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
winit = { version = "0.30", default-features = false, features = ["x11", "wayland", "wayland-csd-adwaita", "rwh_06"] }
skia-safe = { version = "0.97.0", features = ["gl"] }
glutin = "0.32.3"
glutin-winit = "0.5.0"
glow = "0.17.0"
winit = { version = "0.30.13", default-features = false, features = ["x11", "wayland", "wayland-csd-adwaita", "rwh_06"] }
raw-window-handle = "0.6.2"
scopeguard = "1.2"
# Jian path deps —— 双写 path + version per spec §12.2。
# jian-skia: 提供 SkiaBackend (RenderBackend impl) + skia textlayout (textlayout feature
# 需要 ICU + harfbuzz, ~15MBP0.5 已升 skia-safe 0.78→0.97 + 新增 pub draw_on_canvas).
jian-skia = { path = "../../vendor/jian/crates/jian-skia", version = "0.0.1", features = ["textlayout"] }
# jian-host-desktop: target-gate desktop only (Linux/macOS/Windows);不进 android/ios
# metadataTask 1 Step 26 boundary check 验证)。
# - default-features = falseJian 默认 features 含 `run = ["dep:softbuffer"]` 拉
# raster presentOP 自管 GPU event loop 不需要 softbuffer。
# - features = ["textlayout"]:与 jian-skia 对齐文本路径。
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
jian-host-desktop = { path = "../../vendor/jian/crates/jian-host-desktop", version = "0.0.1", default-features = false, features = ["textlayout"] }

View file

@ -1,20 +1,27 @@
//! OpenPencil shell — native (desktop) backend.
//!
//! Per kickoff spec §1.2: this crate must NOT be linked into the wasm32-unknown-unknown
//! web bundle. Even though some deps (winit) compile silently on wasm32 via web-sys,
//! we use an explicit compile_error! guard to make accidental inclusion a hard error.
//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate must NOT be linked into
//! the wasm32-unknown-unknown web bundle. Even though some deps (winit) compile
//! silently on wasm32 via web-sys, we use an explicit compile_error! guard to
//! make accidental inclusion a hard error.
//!
//! Step 1a Task 1 状态:仅装 depsP0-pinned GL stack + jian-skia +
//! jian-host-desktopSharedSkiaContext / NativeBackend 实现在 Task 2 加。
#[cfg(target_arch = "wasm32")]
compile_error!(
"openpencil-shell-native must NOT be compiled for wasm32 targets. \
Use openpencil-shell-web for browser builds (kickoff spec §1.2)."
Use openpencil-shell-web for browser builds (spec v19 §1.2)."
);
pub fn placeholder() -> String {
format!(
"openpencil-shell-native skeleton ({})",
openpencil_shell_core::placeholder()
)
/// Skeleton placeholder until Task 2 lands `SharedSkiaContext` / `NativeBackend`.
///
/// 用 OP `Color::RED` 命名常量证明 shell-core re-export 链通了;这是最小
/// link-check Step 1a Task 1 不实现 Skia surface 与 GL provider那些
/// 是 Task 2 的范围)。
pub fn placeholder() -> &'static str {
let _red = openpencil_shell_core::Color::RED;
"openpencil-shell-native skeleton (Task 1: deps wired)"
}
#[cfg(test)]
@ -23,6 +30,6 @@ mod tests {
#[test]
fn skeleton_links_core() {
assert!(placeholder().contains("openpencil-shell-core"));
assert!(placeholder().contains("Task 1"));
}
}

View file

@ -1,15 +1,21 @@
//! OpenPencil shell — web (wasm32) bundle entry.
//!
//! Per kickoff spec §1.2: this crate is the web bundle entry. CI invariant
//! requires `cargo check --target wasm32-unknown-unknown -p openpencil-shell-web
//! --no-default-features --features web` to pass on every PR.
//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate is the web bundle entry.
//! CI invariant requires `cargo check --target wasm32-unknown-unknown -p
//! openpencil-shell-web --no-default-features --features web` to pass on
//! every PR.
//!
//! Step 1a Task 1 状态:仅 link-check shell-core re-exportCanvasKit
//! WebBackend 在 Step 1b 加。
use wasm_bindgen::prelude::*;
/// Skeleton placeholder until Step 1b lands `WebCanvasKitBackend`.
///
/// 用 OP `Color::TRANSPARENT` 命名常量证明 shell-core re-export 在 wasm32
/// 链通了;这是最小 link-checkshell-core 必须 wasm32-clean per spec §1.2)。
#[wasm_bindgen]
pub fn placeholder() -> String {
format!(
"openpencil-shell-web skeleton ({})",
openpencil_shell_core::placeholder()
)
let _t = openpencil_shell_core::Color::TRANSPARENT;
"openpencil-shell-web skeleton (Task 1: deps wired)".to_string()
}

1
vendor/jian vendored Submodule

@ -0,0 +1 @@
Subproject commit ad13ce6283caf402e71608d362e837b09140331b