The onboarding cards are now actionable:
- 新建/Init → `GitRepo::init` at the saved doc's directory, then
rebind the session so the doc is tracked (disabled until saved);
- 打开/Open → native folder picker → `GitRepo::discover` + a new
`GitSession::bind_repo` (tracks the doc when it's under the repo);
- 克隆/Clone → info dialog for now (needs the in-panel URL form — a
follow-up).
Adds GitPanelHit::{EmptyInit,EmptyOpen,EmptyClone},
GitPanelAction::{InitRepo,OpenRepo,CloneRepo}, host press dispatch +
binary drain, `git_panel.has_saved_file` tracked on every rebind, and
a hit-test test. EN/ZH strings for the two new messages.
Replace the bare "不是 Git 仓库。" text with the TS onboarding empty
state: a History clock glyph in a rounded box, the
"这份文件还没有版本历史" heading, three cards (新建/打开/克隆 with
FilePlus/FolderOpen/GitFork icons + descriptions), and the "Git is
optional" note — laid out centred in a 380×284 panel.
- new History/FilePlus/GitFork icons;
- `GitPanelState.has_saved_file` dims the Init card when the doc is
unsaved (matching TS);
- `empty_state_rects` shared for the upcoming card hit-test.
Card actions (init/open/clone) wired in a follow-up.
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.
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`.
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.
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.
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.
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.
- 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.
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).
- 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).
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.
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.
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).
- 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
- 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
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.
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.