Commit graph

1245 commits

Author SHA1 Message Date
Kayshen-X
50e95fbf89 fix(panels): compile out the git button on web (no git backend)
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 3s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 3s
Rust multi-platform build / linux-x86_64 (push) Failing after 2s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 1s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 2s
The web/wasm build has no git backend and never paints GitPanel, so
the top-bar git button toggled an invisible panel (Codex stop-time
review). Gate the button's paint + hit-test on a `GIT_BUTTON_AVAILABLE`
const (`!cfg!(target_arch = "wasm32")`) so it only exists on desktop.
2026-05-29 22:23:37 +08:00
Kayshen-X
a4b70fa055 feat(panels): add git-panel button to the top bar
The top bar had no affordance to open the git panel (only a
keyboard/menu path). Add a TS-style git button just right of the file
name: a `GitBranch` glyph + the current branch name when in a repo.
Always shown (per request) — a click toggles the git panel, which
offers `init` when the doc isn't yet a repo.

- new `Icon::GitBranch` lucide glyph;
- `TopBar.git_branch` from `git_panel.branch`; `git_button_rect`
  (CJK-aware width estimate so it clears a CJK file name) shared by
  paint + hit-test; `TopBarHit::ToggleGitPanel`;
- native host mirrors `main.rs` toggle bookkeeping (per-frame refresh
  does the scan); web host toggles `git_panel.open`.
2026-05-29 22:12:38 +08:00
Kayshen-X
e2f0e4e1a0 fix(host): render Korean (Hangul) text in chrome
Hangul codepoints were routed to the shared CJK typeface, which is
resolved from a Han ideograph (a Chinese font with no Hangul glyphs),
so 한국어 painted as blank .notdef boxes in the locale picker while
Chinese/Japanese/Hindi/Thai/Vietnamese rendered fine. Add a dedicated
`is_hangul_codepoint` split + a cached `korean_typeface` (resolved from
'한' → Apple SD Gothic Neo / Noto Sans KR) and route Hangul there in
both typeface-resolution paths.
2026-05-29 21:40:37 +08:00
Kayshen-X
1d06be7f0d fix(panels): keep file-menu dropdown anchor in sync with the divider
The new sidebar↔file-menu divider shifted the folder button right, but
`TopBar::file_menu_rect` (the dropdown anchor) still used the pre-divider
x, so the file menu opened left of its button (Codex stop-time review).
Route the anchor through the divider span and have hit_test reuse
`file_menu_rect` so paint / hit-test / anchor can't drift again.
2026-05-29 21:23:46 +08:00
Kayshen-X
21f98dff61 style(panels): align top-bar glyphs + smaller icons + left dividers
Match the TS top bar and the user's refinement feedback:
- icons 16→14px, chevron 12→10px (top-bar-local consts; other widgets
  keep their own sizes) — a touch smaller / more refined;
- route every glyph through `glyph_top(center_y, size)` and center the
  agent chip's icon + 11px text on the shared center line (fixes the
  off-axis drift);
- add a divider between the sidebar toggle and the file-menu (user
  request) and unify all three dividers via `paint_divider`
  (1×14, border@60%, 4px gaps) — TS `w-px h-3.5 bg-border/60 mx-1`;
- keep paint + hit-test in sync for the new divider spans.
2026-05-29 21:16:48 +08:00
Kayshen-X
0f2e751088 feat(host): web parity for zoom-to-fit + rename caret
Mirror the native host on the web backend:
- ArrowLeft/Right move the inline-rename caret (`apply_rename_caret`)
  before falling through to node-nudge;
- StatusBar search icon frames the active page content
  (`status_bar_search_hit` + `zoom_to_fit`, reusing `canvas_region` +
  `LayoutScene::content_bounds` + `Viewport::fit_to`).

Type-checks on the host target; the wasm bundle build still needs EMSDK.
2026-05-29 21:00:14 +08:00
Kayshen-X
567f5f1923 style(panels): refine top-bar agent chip to match TS
- add a 1px×14px divider between the agent/MCP chip and the locale
  button (TS `w-px h-3.5 bg-border/60`) so the status chip reads as a
  separate group;
- chip text 12→11px in muted-foreground, status dot 8→6px in
  emerald-500 (#10b981) — matches the TS chip density/colour.

Note: the chip showing "Agents & MCP" vs "N agent · M MCP" is
connection state (0 connected agents/MCP → the set-up label), not a
layout bug.
2026-05-29 20:52:54 +08:00
Kayshen-X
ac53a47d67 fix(canvas): apply node opacity to raster images
Node-level opacity was folded into fill/stroke/gradient/shadow alpha at
scene-build, but rasters carry no colour to bake into, so image nodes
rendered fully opaque (codex stop-time review). Carry cumulative opacity
on `SceneNode` and modulate the image paint's alpha in the native draw
path (`draw_image_with_options` gains an `opacity` arg; trait default +
web fall back to opaque).
2026-05-29 20:46:23 +08:00
Kayshen-X
82ef39b2ce chore: bump casement to fix(macos) traffic-light reposition idempotent across resize + fullscreen exit 2026-05-29 20:25:25 +08:00
Kayshen-X
b70cbe4530 feat(editor): node opacity, path gradient/inner-shadow fills, zoom-to-fit, rename caret
- canvas: fold node-level `opacity` into resolved fill/stroke/gradient/
  shadow alpha at scene-build (was dropped entirely); render linear/radial
  gradients on SVG path nodes (native skia shader; solid first-stop fallback
  on backends without one); render inset (inner) shadows on path nodes via
  clip + DstOut blur. Fixes the risk-monitor pedestal vs the TS reference.
- editor: StatusBar search icon frames the active page's content in the
  viewport (`Viewport::fit_to` + `LayoutScene::content_bounds`); inline-rename
  input supports ArrowLeft/Right caret movement with char-based mid-string
  insert/delete (CJK-safe).
- also carries in-progress flip/text canvas transforms, web backend, and
  figma-probe tweaks already present in the working tree, plus a clippy
  needless-borrow/closure cleanup in op-orchestrator (1.94 gate).
2026-05-29 20:24:36 +08:00
Fini
34054fce6a chore: bump casement to fix(macos) launch on demand
Some checks failed
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 3s
Rust check (native) / cargo-deny (native) (push) Failing after 2s
Rust check (native) / diagnostics golden drift (push) Failing after 3s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 2s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 1s
Rust check (native) / macos-latest / 1.94 (push) Has been cancelled
Rust check (native) / windows-latest / 1.94 (push) Has been cancelled
Rust multi-platform build / linux-aarch64 (push) Has been cancelled
Rust multi-platform build / macos-aarch64 (push) Has been cancelled
Rust multi-platform build / windows-x86_64 (push) Has been cancelled
Rust multi-platform build / macos-x86_64 (push) Has been cancelled
Rust multi-platform build / windows-aarch64 (push) Has been cancelled
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Has been cancelled
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Has been cancelled
2026-05-28 00:10:24 +08:00
Fini
bb4c62519d fix(orchestrator): clamp mobile status-bar levels to root width
The mobile status-bar chrome injected by `mobile_status_bar_json`
hardcoded levels.x=286 — the right-aligned cellular/wifi/battery
group, sized for a 390-wide iPhone reference. When the C2 plan
work started honouring explicit prompt sizes via
`explicit_mobile_size` (accepts 240..=520 wide), a 320 x 568
iPhone-SE prompt rendered the chrome at x=286..364 — 44 px past
the right edge.

Take root `width` as an argument and derive levels.x as
`width - 78 - 26` (78 = chrome width, 26 = iOS safe-area gutter,
matches the existing 390 reference: 390 - 104 = 286). New test
exercises 320-wide root to lock in the no-overflow contract.
2026-05-27 23:19:45 +08:00
Fini
07277b381d refactor(host): split tests + trim comments for 800-line cap
chat_subprocess.rs (850) and design_session.rs (807) blew past
the ceiling after the Codex provider + design-viewport work
landed in 63f787c3. Move their inline `mod tests` bodies out to
sibling `*_tests.rs` files via `#[cfg(test)] #[path = …]
mod tests;`.

app_handler.rs (805) is one bare `impl ApplicationHandler` with
no natural split point; trim the file-level doc and two inline
comments in `exiting` to land at exactly 800.

After: chat_subprocess.rs 650, design_session.rs 508,
app_handler.rs 800. 114 host-desktop tests pass.
2026-05-27 23:19:45 +08:00
Fini
c2a546a363 refactor(orchestrator): split tests into sibling files for 800-line cap
cleanup.rs / prompt.rs / scaffold.rs all blew past the repo's
800-line ceiling after the typography + mobile-screen work landed
in d26b5685. Move each file's inline `mod tests` body out to a
sibling `*_tests.rs` and re-attach via `#[cfg(test)] #[path = …]
mod tests;` — same pattern the existing `cleanup_tests_c1.rs`
uses.

After split: cleanup.rs 772, prompt.rs 313, scaffold.rs 422.
Test counts unchanged (612 orchestrator tests pass).
2026-05-27 23:19:45 +08:00
Fini
9efef80ba2 feat(host): wire Codex CLI provider and polish design-turn launch
- chat_subprocess: wire Codex CLI through SubprocessProvider via
  `codex exec --json` + positional-arg prompt, and parse the new
  `item.completed` agent_message events into TextDelta
- chat_session: route chat_selected_agent=1 through the Codex
  provider; on design-turn launch, clear the fresh starter frame
  so generated nodes don't stack on top of the demo content
- design_session: fit_design_viewport_to_content keeps generated
  designs centred while the orchestrator streams nodes
- app_handler: thread viewport size through pump_commands so the
  fit step has canvas dimensions to work with
2026-05-27 23:19:45 +08:00
Fini
ca3ed27348 feat(orchestrator): mobile screen plans, typography cleanup, parse fixes
- plan: when the prompt looks like a mobile screen (or carries an
  explicit WxH), emit a top-summary + main-content fallback plan
  instead of the generic three-section split
- cleanup_typography: new module + repair_overbold_text_hierarchy
  to soften LLM over-bolding when >=65% of text nodes come back as
  weight >=700
- parse: default missing/null image src to "" so generated image
  nodes no longer fail strict deserialization
- plan_normalize / scaffold / prompt / subagent: associated reworks
  feeding the above paths
2026-05-27 23:19:44 +08:00
Fini
7b8b506f4a refactor(editor): collapse panels and drop unused host modules
Bundle three cleanups that share glue files (widget_host, lib.rs,
widgets/mod.rs) and so must land together:

- collapse variables_panel/{header,paint,tests}.rs into
  variables_panel.rs and fold the four host variables_panel_*.rs
  modules into the main widget_host
- remove property_panel_font_picker along with the system_fonts
  loader thread and the system_font_rx plumbing on WidgetHostNative
- delete the widget_host keyboard_motion module and roll its logic
  into keyboard.rs

Drops the now-orphan themed-variable scalar setters,
variables.type{Color,Number,String} i18n keys (×15 locales), and
the Cmd+V / Cmd+D shortcuts that targeted the removed panels.
2026-05-27 23:19:44 +08:00
Kayshen-X
2ab2a86fdc fix(editor): improve rust parity and figma import performance
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 2s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 1s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 2s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 2s
2026-05-27 21:51:57 +08:00
Kayshen-X
f547fe1737 feat(editor): improve native panel parity
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 2s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 1s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 2s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 1s
2026-05-26 21:36:03 +08:00
Fini
e1ba222e87 fix(canvas): align rust z-order with renderer
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 2s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 1s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 2s
2026-05-26 07:02:32 +08:00
Kayshen-X
b0b52a7842 feat(panels): improve native property controls and icons
Some checks failed
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 2s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 1s
Rust check (native) / macos-latest / 1.94 (push) Has been cancelled
Rust check (native) / windows-latest / 1.94 (push) Has been cancelled
Rust multi-platform build / linux-aarch64 (push) Has been cancelled
Rust multi-platform build / macos-aarch64 (push) Has been cancelled
Rust multi-platform build / windows-x86_64 (push) Has been cancelled
Rust multi-platform build / macos-x86_64 (push) Has been cancelled
Rust multi-platform build / windows-aarch64 (push) Has been cancelled
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Has been cancelled
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Has been cancelled
2026-05-24 23:30:00 +08:00
Fini
84e2b5f2bf fix(host): inline orchestrator system prompt into user message
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 2s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 2s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 2s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 2s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 1s
Codex stop-time review caught a silent prompt drop in the
`ChatProviderLlmClient` adapter from the previous commit. I put the
orchestrator's `CallRequest.system_prompt` into
`ChatRequest.system_prompt`, but all three CLI-backed `ChatProvider`
impls (`ClaudeCodeProvider`, `CopilotProvider`, `SubprocessProvider`)
ignore that field — they drive their respective CLIs through
subprocess / SDK channels with no per-turn system-prompt slot. Net
effect: the planner / sub-agent role prompt was discarded and the
model only saw the bare user prompt.

Fix matches the precedent in `chat_runtime::BuiltInProvider::send`
(line 138): prepend the system prompt to the user message with a
`\n\n---\n\n` separator, then leave `ChatRequest.system_prompt`
empty. If a CLI later grows a real system-prompt channel, unwrap the
prepend back into the field.

cargo test -p op-host-desktop 106 passed; fmt + clippy clean.
2026-05-24 20:45:45 +08:00
Fini
4f9665227b refactor(host): orchestrator uses chat-panel agent, drop Anthropic-key dependency
User pointed out: the desktop chat panel already has 5 CLI agents
(Claude Code / Codex / OpenCode / Copilot / Gemini) that manage their
own auth — Claude Code is logged in by the user, Copilot rides GitHub
auth, Gemini rides gcloud. The #27 intent-gate landing required a
separate `OPENPENCIL_ANTHROPIC_API_KEY` env var to launch the
orchestrator, which broke UX consistency and was a real design defect:
the existing CLI agents already provide LLM access, the orchestrator
should reuse them instead of requiring users to wire up a parallel
direct-API key.

Root cause was a trait mismatch: `Orchestrator` needs an
`LlmClient` impl, and `DesktopLlmClient` (now deleted) was written to
take `Arc<dyn agent::Provider>` — but `agent::Provider` is only
implemented by `AnthropicProvider` / `OpenAiCompatProvider`, NOT the
CLI-backed `ChatProvider`s (`ClaudeCodeProvider` etc).

Fix is a thin `ChatProvider → LlmClient` adapter:

- New `chat_provider_llm::ChatProviderLlmClient` (~80 lines): owns
  an `Arc<dyn ChatProvider>`, each `LlmClient::call` spawns a thread
  that drains the provider's blocking iterator into a futures mpsc
  channel and returns the receive half as a `BoxStream`. Same
  async↔sync bridge `BlockingRecvIter` uses in the opposite
  direction.
- `DesignSession::start` now generic over `L: LlmClient + Send +
  'static` instead of taking `Arc<dyn Provider>` + default_model;
  caller picks the LlmClient impl. Test e2e path
  (`from_channels`) unaffected.
- `chat_session::launch_if_pending`: Design branch reads
  `chat_selected_agent`, wraps the existing `provider_for_agent`
  output in `ChatProviderLlmClient`, hands it to `DesignSession`.
  Unwired agents (Codex / OpenCode) fall through to the chat-path
  unwired-agent error bubble — same UX as a chat send to an
  unwired agent.

Deletions:

- `chat_orchestrator.rs` — `DesktopLlmClient` was its last
  surviving content; with the new adapter no caller needs it.
  Removed the file + the `mod chat_orchestrator;` line.
- `chat_session::provider_for_design` and the
  `OPENPENCIL_ANTHROPIC_API_KEY` / `ANTHROPIC_API_KEY` /
  `OPENPENCIL_ORCHESTRATOR_MODEL` env reads.
- `op-host-desktop/Cargo.toml` `agent` crate's `["anthropic"]`
  feature — no path inside the host needs an `agent::Provider`
  impl anymore (the trait stays imported for the
  `BuiltInProvider` shim in `chat_runtime.rs`, future-facing).

`op-smoke` keeps `AnthropicProvider` + `OpenAiCompatProvider`
directly because the smoke deliberately bypasses any CLI auth path
to validate the orchestrator against a raw API endpoint
independently of the host UI.

cargo test -p op-host-desktop 106 passed (unchanged). cargo fmt
--all -- --check + cargo clippy --workspace --all-targets -- -D
warnings clean.
2026-05-24 20:41:36 +08:00
Fini
dbd5f1dc09 Merge branch 'v0.8.0-new' of github.com:ZSeven-W/openpencil into v0.8.0-new 2026-05-24 19:45:55 +08:00
Fini
91883ec5d6 style(smoke): apply rustfmt to op-smoke main.rs
Codex stop-time review caught: op-smoke was not rustfmt-clean. Three
hunks — outer `let model = ...unwrap_or_else` had its match body
inlined; one `eprintln!` line exceeded 100 chars and needed
expansion; one other formatting nit.

The previous commit's `cargo fmt --all -- --check` invocation reported
FMT_OK because the output scrolled past the diff hunks before I saw
them — the exit code path Codex relies on (not the truncated grep
output I read) caught the residual diffs. Lesson for the runbook:
trust the exit code, not eyeballing tail output.

`cargo fmt --all -- --check` clean now; `cargo clippy --workspace
--all-targets -- -D warnings` clean.
2026-05-24 19:03:10 +08:00
Fini
0477df456a feat(smoke): support OpenAI-compatible providers (DeepSeek/方舟/百炼/Moonshot)
Smoke binary now picks a Provider via `OPENPENCIL_LLM_PROVIDER`:

- `anthropic` (default) — `AnthropicProvider` + `OPENPENCIL_ANTHROPIC_API_KEY`
  (or legacy `ANTHROPIC_API_KEY`).
- `openai-compat` (or `openai`) — `OpenAiCompatProvider` +
  `OPENPENCIL_LLM_API_KEY` + `OPENPENCIL_LLM_BASE_URL`. Standard dialect;
  works against DeepSeek, 火山方舟, 百炼, Moonshot, OpenRouter, OpenAI,
  any OpenAI-compat vendor.

`agent` feature flags bumped from `["anthropic"]` to
`["anthropic", "openai"]` to pull in the `async-openai`-backed provider.

Default model selection branches on provider:
- anthropic → `claude-sonnet-4-6`
- everything else → `gpt-4o-mini` (override via
  `OPENPENCIL_ORCHESTRATOR_MODEL`)

Verified end-to-end against 方舟 CP / glm-5.1 — pipeline topology works
through to a partial-success `Ok` summary. Full run details + the
non-Claude quality finding (`stroke.fill` accepting bare string vs
expected sequence) archived in openpencil-docs
`superpowers/notes/2026-05-24-orchestrator-headless-smoke-result.md`.
2026-05-24 19:00:03 +08:00
Kayshen-X
d905369ec9 fix(editor): improve shape controls and drag snapping 2026-05-24 18:46:10 +08:00
Fini
ec8ac7984c chore: lockfile update for op-smoke crate added in e2c6e74b 2026-05-24 18:43:20 +08:00
Fini
e2c6e74b42 feat(smoke): headless op-orchestrator smoke runner (task #28)
New `op-smoke` binary crate — drives one `Orchestrator::run` against
`AnthropicProvider` without the desktop UI / `DesignSession` actor model,
single-threaded `block_on` on an inline `DocSink`, every event dumped to
stderr. Decouples task #28's "does the pipeline reach the LLM and apply
EditorCommands correctly" verification from GUI smoke (canvas paint /
chat panel rendering), which still needs the desktop binary.

Usage:

    export OPENPENCIL_ANTHROPIC_API_KEY=sk-ant-...
    cargo run -p op-smoke -- "design a login screen"

Optional `OPENPENCIL_ORCHESTRATOR_MODEL` (default `claude-sonnet-4-6`).

What's traced to stderr:
- `[SMOKE]` model + prompt
- `[LLM]` per-call system/user lengths + engine errors
- `[PROGRESS]` every `op_orchestrator::Progress` variant (Planning,
  ScaffoldDone, SubtaskStarted/Done/Failed, CleanupDone, validation +
  visual-ref variants)
- `[CMD]` per `EditorCommand::apply` one-line label + applied result
  (InsertSubtree shows parent_id + nodes.len(); SetNodeStrokeHex shows
  hex; etc.)
- `[UNDO]` begin / end batch boundaries
- `[FINAL]` Ok/Err + elapsed; on Ok prints root_frame_id, total_nodes,
  per-subtask outcomes

What this verifies vs the desktop GUI smoke
(`superpowers/notes/2026-05-24-orchestrator-smoke-steps.md`):
- LLM client construction (`AnthropicProvider::new` + auth)
- Network reachability (200 / 401 / 429 surfaces as `LlmError`)
- Planner → scaffold → subtask → cleanup transitions
- `InsertSubtree` ID-remapping (via post-apply state)
- Terminal `RunSummary` / `OrchestratorError`

What it deliberately skips:
- Canvas paint correctness — run the desktop binary
- chat panel progress rendering — run the desktop binary
- Pre-validation fixes — smoke uses `SkippedPreValidator` so the
  trace stays focused on orchestrator behaviour; the desktop binary
  uses `LintPreValidator`

`op-host-desktop::chat_orchestrator::DesktopLlmClient` would have been
ideal to reuse, but the host crate is binary-only (no lib target); the
op-smoke `SmokeLlmClient` is a ~50-line copy that swaps
`shared_runtime().spawn` for `tokio::spawn` (smoke owns its own tokio
runtime via `#[tokio::main]`).

cargo fmt + clippy --workspace --all-targets -D warnings clean.
Sanity-checked the no-prompt and no-API-key error paths.
2026-05-24 18:42:56 +08:00
Kayshen-X
743e8a902c fix(panels): render image fill controls 2026-05-24 17:45:53 +08:00
Kayshen-X
b6e9e91464 chore: enforce rust pre-commit checks 2026-05-24 17:44:42 +08:00
Fini
fa2d06c5af refactor(ai): drop dead node_base_role helper in validation_fixes_apply
memory-tagged follow-up — `node_base_role(node) -> Option<&str>` (line
568 of `validation_fixes_apply.rs`) was a thin shim over
`node.base().role.as_deref()` with zero call sites anywhere in
crates/* or vendor/*. Inlining at the (single, hypothetical) caller is
shorter than the helper itself.

`cargo test -p op-orchestrator` still 585 passed; workspace 1880
passed; fmt + clippy --workspace -D warnings clean.
2026-05-24 16:41:36 +08:00
Fini
91f05a1dc5 refactor(host): drop dead chat_orchestrator code now that DesignSession owns the route
Task #27 swapped the orchestrator route from `chat_orchestrator::run_design_request`
(direct `block_on` on the UI thread, never called) to
`design_session::DesignSession::start` (worker thread + RemoteDocSink +
ack channel). Confirming the leftover types are truly dead:

- `DesktopDocSink` — grep finds zero callers; replaced by `RemoteDocSink`
  in `design_session.rs` which owns its own mirror + sends `EditorCommand`
  back to the UI thread over mpsc.
- `run_design_request` — grep finds zero callers; replaced by the
  direct `Orchestrator::new().run(...)` invocation inside the worker
  closure in `DesignSession::start`.
- `DesktopLlmClient` — actively used (`design_session::start` builds one
  per turn). Kept.

So this commit:

- Deletes `DesktopDocSink` struct + impl block (~30 lines).
- Deletes `run_design_request` async fn (~40 lines).
- Removes `#![allow(dead_code)]` — the surviving code (`DesktopLlmClient`)
  is reachable so the module no longer needs the blanket allow.
- Trims module doc — drops the 4-item TODO list that described the
  pre-#27 wiring gap (intent gate / threading model / undo batch /
  validation providers) since #27 + #35 closed all four.
- Drops unused imports (`EditorCommand`, `EditorState`, `DesignRequest`,
  `Orchestrator`, `OrchestratorError`, `Progress`, `RunSummary`,
  `DocSink`, `AbortFlag`, `SkippedScreenshotProvider`,
  `SkippedVisionLlmClient`, `ValidationProviders`).
- Touches the `design_session.rs` comment that referenced `DesktopDocSink`
  by name — rephrased to describe the no-op behaviour without the
  dangling pointer.

`cargo test -p op-host-desktop` 106 passed (unchanged). cargo fmt +
clippy --workspace -D warnings clean.
2026-05-24 16:16:40 +08:00
Fini
22fe77b664 test(host): end-to-end actor-channel round-trip for DesignSession
Task #28 prep. The orchestrator side has its own end-to-end tests
under `crates/op-orchestrator/`, but the host's actor seam
(worker → mpsc → UI thread → ack → mirror update + Progress → chat
bubble) was only exercised piecewise by the three `RemoteDocSink`
tests. This adds a single end-to-end smoke that drives both
`pump_commands` + `pump_progress` from a fake worker thread against a
real `WidgetHostNative`, then asserts:

- Progress events rendered into the trailing assistant bubble
  (`• Planning…` line surfaces verbatim).
- `RunSummary::Ok` rendered as the `Done — K subtask(s) succeeded`
  trailer with `streaming = false`.
- `*current` cleared after `Done` so the event loop stops ticking
  at 33ms for this turn.

Internal hook: `DesignSession::from_channels` (cfg(test)) wraps
externally-supplied channels so a fake worker can drive the UI
pumps without spinning up `Orchestrator::run` / an `agent::Provider`
/ a live API key. Production path still goes through
`DesignSession::start`.

The full LLM smoke is documented separately in
openpencil-docs/superpowers/notes/2026-05-24-orchestrator-smoke-steps.md
— it needs `OPENPENCIL_ANTHROPIC_API_KEY` and can't live in cargo test.

cargo test -p op-host-desktop 106 passed (was 105). cargo fmt + clippy
--workspace -D warnings clean.
2026-05-24 15:35:25 +08:00
Fini
bba8e22f67 fix(host): drop in-flight design session when falling to chat path
Codex stop-time review caught a real cross-session leak in the intent
gate introduced by the previous commit. `launch_if_pending` clears
`current_chat` when the design branch is taken, but the chat branch
only cleared `current_chat` (for the unwired-agent error path) — it
never cleared `current_design`.

Symptom: if a design turn is in flight (worker pumping `apply` requests
+ `Progress` deltas into the trailing assistant bubble) and the user
sends a chat message, the new chat bubble begins streaming chat deltas
while the still-running design worker keeps acking apply requests on
the canonical doc and appending `Progress` strings to the same bubble.
The fix mirrors the design branch — when the chat path is taken (chat
intent, design intent without a Provider, or any unwired agent),
`*current_design = None` drops the worker's command receiver so its
next `apply` returns false and the turn ends.

`cargo test -p op-host-desktop` 105 passed. workspace fmt + clippy
clean.
2026-05-24 15:16:07 +08:00
Fini
35312b960b feat(host,orchestrator): wire intent gate + DesignSession into chat runtime
Task #27. Routes user messages through `op_orchestrator::classify_intent`
in `chat_session::launch_if_pending` — `Intent::Design` with a configured
`agent::Provider` launches into the orchestrator pipeline; everything
else (chat intent, or design intent with no Provider) falls through to
the existing `ChatProvider` CLI path.

The orchestrator is `async + &mut EditorState`; the chat path is built
around worker threads + blocking iterators (`ChatProvider::send`). Codex
architectural review picked Option E (UI-owned actor + RemoteDocSink +
ack channel):

- Worker thread owns a `RemoteDocSink` that forwards each `apply(cmd)`
  over an mpsc channel to the UI thread, which `apply()`s on the real
  `EditorState` and replies with an ack carrying a fresh state snapshot.
- `RemoteDocSink::state()` reads from a locally cached mirror updated
  by each ack — covers `EditorCommand::InsertSubtree`'s ID-remapping +
  history bookkeeping which must run on the UI thread.
- Two mpsc channels per turn: progress deltas (Planning / SubtaskStarted
  / etc.) into the chat transcript, and `DesignCmdReq` for apply +
  undo-batch boundaries.
- `BeginUndoBatch` / `EndUndoBatch` are forwarded as own `DesignCmdOp`
  variants so the UI can route them through real history batching once
  `op-editor-core` exposes that API (currently no-op, matching
  `DesktopDocSink`).

Provider source MVP: `OPENPENCIL_ANTHROPIC_API_KEY` (preferred) or
`ANTHROPIC_API_KEY` from env constructs an `AnthropicProvider`. Model
override via `OPENPENCIL_ORCHESTRATOR_MODEL` (defaults to
claude-sonnet-4-6). When neither key is set, design intent falls back
to the chat-CLI path so the user still gets an answer.

Wiring:
- `main.rs` `current_design: Option<DesignSession>` field next to
  `current_chat`; mutually exclusive routing in `launch_if_pending`.
- `app_handler.rs` `RedrawRequested` pumps both `pump_commands`
  (apply + ack) and `pump_progress` (deltas + summary); WaitUntil tick
  schedules a 33 ms wake when either session is in flight.
- `keyboard_input.rs` Enter / send sites pass both Option<&mut> args.

Provider features: `agent` crate gains the `anthropic` cargo feature in
`op-host-desktop/Cargo.toml` so `AnthropicProvider` is reachable
(previously `default-features = false` left it gated out — chat path
only needed the trait + engine wiring).

3 new `RemoteDocSink` tests cover ack round-trip + closed-channel safety
+ undo-batch signal distinguishability. Workspace 1882 passed / 0
failed. cargo fmt + clippy --workspace -D warnings clean.

The `chat_orchestrator.rs::run_design_request` legacy entry stays for
now (used by future single-shot programmatic callers); the live path is
`DesignSession::start`. Validation providers stay `Skipped*` until
jian-skia `captureRegion` + vision LLM crate land (task #28).
2026-05-24 15:11:34 +08:00
Kayshen-X
ad555231e4 fix(panels): keep image fill popover interactive
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 3s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 2s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 1s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 1s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 2s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 2s
2026-05-24 14:01:38 +08:00
Kayshen-X
66d58f2b09 feat(editor): add hover wash to vertical toolbar buttons
Mirror the existing file_menu / locale_picker / shape_picker hover
pattern: state-layer ToolbarHover enum on EditorUiState, widget reads
it during paint and renders theme.button_hover, host updates it on
apply_cursor_move AFTER drag detection so a path-anchor / node / pan
drag whose cursor crosses the toolbar isn't intercepted.

Extracted into widget_host/toolbar_hover.rs to keep geometry.rs under
the 800-line cap.
2026-05-24 14:01:38 +08:00
Fini
28f16e6d91 Merge branch 'v0.8.0-new' of github.com:ZSeven-W/openpencil into v0.8.0-new 2026-05-24 13:03:26 +08:00
Fini
722b0f4114 fix(editor-core,host): SetNode{Stroke,Fill}Hex accept $variable refs
Codex stop-time review caught a real regression in LintPreValidator:
when the doc declares a `color-border` variable, op-design-lint's
`border_stroke()` emits the suggested stroke fill as `$color-border`
(a design-token reference, not raw hex). The host adapter decomposes
`SetStroke` into `SetNodeStrokeHex` + `SetNodeStrokeWidth`. But
`cmd_set_node_stroke_hex` strict-parsed the hex via `parse_hex_rgb`
and rejected the `$`-prefixed string with `return false`, while
`SetNodeStrokeWidth` still applied successfully — so
`any_applied = true` and `applied_count += 1` overreported success
while the stroke color was silently dropped.

The TS counterpart (`fixes.rs::set_stroke_from_json` via
`serde_json::from_value::<PenStroke>`) treats `color` as a free
`String` and accepts `$ref` verbatim. The underlying
`set_primary_{stroke,fill}_hex` already writes `slot.color =
hex.to_string()` unconditionally — only the cmd-layer gate was wrong.

Fix:
- `cmd_set_node_stroke_hex` + `cmd_set_node_fill_hex` (op-editor-core):
  let `$`-prefixed strings bypass `parse_hex_rgb`. Literal hex paths
  unchanged; doc-comment notes the design-token contract.
- New fixture `invisible-container-with-var.json` (op-design-lint):
  doc declares `color-border` var so the detector emits
  `$color-border` ref. Regen'd golden confirms the ref reaches the
  Issue's `suggestedValue`.
- New equivalence test `equivalence_invisible_container_with_var` in
  `plan.rs`: `detect_and_plan + apply` produces same doc as
  `detect_and_fix`, AND the plan carries the `$color-border` ref
  (not a resolved hex).
- New parity test `parity_invisible_container_with_var` in
  `pre_validator.rs`: cross-crate adapter round-trip preserves the
  ref through EditorCommand application.

All 5 `Skipped*Provider` and 4 `PreValidator`-path stubs still in
place. drift guard re-runs clean (14 goldens, same as committed).

`cargo test` op-design-lint 143+13 / op-host-desktop 102 /
op-editor-core 280 / op-orchestrator 585+1 all green.
`cargo clippy --workspace -- -D warnings` clean.
`cargo fmt --all -- --check` clean.
2026-05-24 12:57:31 +08:00
Kayshen-X
f324a6fedb feat(editor): add hover wash to vertical toolbar buttons
Mirror the existing file_menu / locale_picker / shape_picker hover
pattern: state-layer ToolbarHover enum on EditorUiState, widget reads
it during paint and renders theme.button_hover, host updates it on
apply_cursor_move AFTER drag detection so a path-anchor / node / pan
drag whose cursor crosses the toolbar isn't intercepted.

Extracted into widget_host/toolbar_hover.rs to keep geometry.rs under
the 800-line cap.
2026-05-24 12:39:14 +08:00
Fini
061737d32d feat(host): LintPreValidator adapter — wire op-design-lint into PreValidator
Implements the D′ architecture host-side half:
- New `src/pre_validator.rs` with `LintPreValidator` struct and full
  `impl PreValidator` that calls `op_design_lint::detect_and_plan()`
  and translates each `PlannedFix → Vec<EditorCommand>` via
  `planned_fix_to_commands`.
- `ClearEffects` handled by reading current effect count from
  `sink.state()` and emitting N × `RemoveNodeEffect` highest→lowest.
- `SetStroke` decomposes into `SetNodeStrokeHex` + `SetNodeStrokeWidth`
  to work around the absence of a full-PenStroke EditorCommand.
- Parity tests in `#[cfg(test)]` (binary crate, can't use tests/) prove
  `LintPreValidator + EditorState` produces an equivalent document to
  `detect_and_fix` for 5 fixtures (invisible container, text explicit
  height, stacked horizontal padding, unexpected rotation, text effect).
  JSON comparison normalises away the `children: None→Some([])` side
  effect in the EditorState walker.
- `chat_orchestrator.rs` swaps `SkippedPreValidator` → `LintPreValidator`.
- `op-design-lint` added to `op-host-desktop/Cargo.toml` dependencies.
2026-05-24 12:35:02 +08:00
Fini
4fccaa65c7 feat(design-lint): editor-agnostic PlannedFix + detect_and_plan API
Add `PlannedFix` / `PlannedAction` types and `detect_and_plan()` in a
new `plan.rs` module. `detect_and_plan` runs `detect_all` then applies
the same filter as `apply_fixes` (skip Info severity, skip Fill no-op,
skip Remove on status-bar nodes) and returns a `Vec<PlannedFix>` — an
editor-agnostic plan that host adapters can translate to EditorCommands.

`PlannedAction` covers all 8 mutation paths in `apply_fixes` today:
`Remove`, `SetHeightFitContent`, `SetRotation`, `SetCornerRadius`,
`SetFontSize`, `ClearEffects`, `SetPadding`, `SetStroke`.

Adds 6 equivalence tests asserting that applying each `PlannedFix` via
`node_mut` primitives produces a document structurally identical to
`detect_and_fix` on the same input (invisible-container, text-explicit-
height, stacked-horizontal-padding, empty-path, status-bar guard,
unexpected-rotation fixtures). All 142 + 13 parity tests pass.
2026-05-24 12:24:52 +08:00
Kayshen-X
2e65c17d18 fix(editor): wire rust ui interactions 2026-05-24 09:31:46 +08:00
Fini
c3b8cd4a44 Merge branch 'v0.8.0-new' of github.com:ZSeven-W/openpencil into v0.8.0-new
Some checks failed
Rust check (native) / macos-latest / 1.94 (push) Waiting to run
Rust check (native) / windows-latest / 1.94 (push) Waiting to run
Rust multi-platform build / linux-aarch64 (push) Waiting to run
Rust multi-platform build / macos-aarch64 (push) Waiting to run
Rust multi-platform build / windows-x86_64 (push) Waiting to run
Rust multi-platform build / macos-x86_64 (push) Waiting to run
Rust multi-platform build / windows-aarch64 (push) Waiting to run
Rust multi-platform build / ios-aarch64 (cargo check only) (push) Waiting to run
Rust multi-platform build / ios-aarch64-sim (cargo check only) (push) Waiting to run
Rust check (native) / ubuntu-latest / 1.94 (push) Failing after 4s
Rust check (native) / cargo-deny (native) (push) Failing after 1s
Rust check (native) / diagnostics golden drift (push) Failing after 2s
Rust multi-platform build / linux-x86_64 (push) Failing after 1s
Rust multi-platform build / wasm32-unknown-unknown / op-host-web (compile guard) (push) Failing after 1s
Rust multi-platform build / android-aarch64 (cargo check only) (push) Failing after 1s
Rust multi-platform build / android-x86_64 (cargo check only) (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo check --target wasm32-unknown-unknown (push) Failing after 1s
WASM bundle check (kickoff §1.2) / cargo-deny --target wasm32-unknown-unknown check bans (push) Failing after 1s
2026-05-24 01:52:13 +08:00
Kayshen-X
bf4ac35966 fix(ci): restore diagnostics golden formatting 2026-05-24 00:53:05 +08:00
Fini
6104fa277a fix(ci): protect op-design-lint goldens from oxfmt + restore TS-oracle format
CI 'Fail on golden drift' (rust-check.yml) was red because oxfmt
inlined the 4-element padding arrays in the committed goldens, while
the TS oracle's `JSON.stringify(value, null, 2)` emits them multi-line.
Every `bun run format` would re-introduce the drift.

Fix:
- .prettierignore: ignore crates/op-design-lint/tests/fixtures/golden/
  so oxfmt leaves them at the TS-oracle byte-exact format.
- regenerate the two affected goldens via the oracle
  (edge-section-padding.json, stacked-horizontal-padding.json) so they
  match what the CI drift guard expects.

`bun run tools/dump-diagnostics-golden.ts` + `git diff` now clean.
`cargo test -p op-design-lint` 149/149 green. fmt-check clean.
This was the only red workflow on the v0.8.0-new push; CI should be
fully green after this lands.
2026-05-24 00:50:31 +08:00
Fini
845e5ae7b5 Merge branch 'v0.8.0-new' of github.com:ZSeven-W/openpencil into v0.8.0-new 2026-05-24 00:39:09 +08:00
Kayshen-X
a124db05f1 fix(ci): clear rust check failures 2026-05-24 00:29:43 +08:00
Kayshen-X
22590b3119 feat(editor): enable figma import drop zone 2026-05-24 00:29:05 +08:00