Commit graph

275 commits

Author SHA1 Message Date
Kayshen-X 5b0d1edb4c test: decouple document fixtures from product releases 2026-07-15 21:17:20 +08:00
Kayshen-X 3a41f2ea7f feat(web): make credential persistence deployment-aware 2026-07-14 23:36:37 +08:00
Fini 44a4fe4e24 feat(editor): align canvas layout and sizing behavior 2026-07-14 00:29:58 +08:00
Fini f04e0a8fb3 fix(editor): give the cursor a clean white rim that cannot jag
The rim was a polygon STROKE, and the trait's fallback draws a polygon
stroke as one capped line per edge - so every vertex of the densely
sampled arc notched and the rim's width visibly wobbled. Two fixes: the
native backend gains a real single-path, round-joined, antialiased
polygon stroke (every caller benefits), and the cursor's rim is now
FILLED geometry - the silhouette outset by an exact 1.6px, painted white,
then covered by the body. The halo layers clear the rim so the outline
reads crisp against both light and dark designs.
2026-07-14 00:29:54 +08:00
Fini e63eb05bba feat(ui): user-selectable pencil cursor styles and Pencil-style narration flow
Settings > System gains a pencil-cursor picker - five silhouettes
(classic, rounded, chubby with a pink eraser, crayon, round-nib marker)
drawn as live swatches, rounded as the default, choice persisted in
~/.openpencil/ui.json and restored on launch. Design-loop narration
additionally streams as visible prose instead of folding into the
collapsed thinking area, and every tool call stamps the content offset
where it landed so the transcript can interleave prose with per-call
verb chips.
2026-07-12 12:32:59 +08:00
Kayshen-X 206bae5b2b test(renderer): satisfy clippy for glyph coverage 2026-07-12 09:15:49 +08:00
Kayshen-X fb95d6b8b8 fix(editor): preserve scoped instance history states 2026-07-12 09:11:49 +08:00
Kayshen-X cb0842a5c9 feat(panels): reorder multiple fill layers 2026-07-12 09:10:49 +08:00
Kayshen-X 3a825cbfe1 feat(canvas): paste clipboard images as nodes 2026-07-12 09:07:49 +08:00
Kayshen-X 4fb6510ce0 test(renderer): cover Cyrillic design text fallback 2026-07-12 09:06:49 +08:00
Fini 075b1f25eb refactor(ui): retire the fixed 'Pencil it out' checklist panel
The pinned checklist duplicated what the transcript already renders
inline (action steps, subtask cards, verb chips, agent narration - the
Pencil-style reading flow), while eating a third of the panel's height.
The transcript is now the single progress surface; the checklist
widget, its hit/scroll wiring on both hosts and its ChatState fields
are removed.
2026-07-12 03:34:36 +08:00
Fini d79604f9ae feat(editor): property edits on component-instance children
Selecting a node INSIDE a component instance now routes property-panel
writes (color, stroke, size) through descendant overrides on the owning
Ref via virtual child anchors, instead of silently doing nothing.
Scope-time history snapshots are repaired against the routed Ref (both
the ref id and the routed display id) on the Arc-shared snapshot store,
so undo never resurrects a detached instance copy.
2026-07-12 02:55:42 +08:00
Kayshen-X 355d94e244 feat(canvas): device-frame preview with switcher, scroll and pinned nav
Preview now presents ONE framed root inside a fixed device frame —
Phone 390x844 / Desktop 1440x900, inferred from the root width
(<=500 -> Phone) with a floating 3-segment switcher (Phone / Desktop /
Canvas, i18n'd across all 15 locales) overriding inference until exit.
Content keeps authored size (no reflow, no content scaling); overflow
scrolls vertically inside the frame, clamped to the pinned-nav top
bound. A bottom nav (semantics.role=nav bottom-anchored, or the
flush/full-width/<=120px heuristic) pins to the frame bottom in a
second clipped paint pass and the content viewport shrinks past it.

One DeviceFrame struct owns every transform: paint, the screen->scene
inverses (strip + letterbox dead zones resolve at surface-resolution
time), and the per-gesture presentation capture, so drags never flip
surfaces mid-gesture. Device mode fails closed — no wheel / pan /
pinch / modifier-zoom ever reaches the editor viewport (pinch gets its
own host entry, split from the line-wheel path at the desktop call
sites). Reconcile now reports { repaint, switched } so warning-only
passes stop recentering; screen switches re-infer, reset scroll and
rebuild the frame.
2026-07-11 04:00:24 +08:00
Kayshen-X 35ce39634b fix(canvas): preview renders design-canvas geometry with pointer capture
Preview scenes now take paint from the promoted document but GEOMETRY
from the unpromoted layout tree via the design canvas's exact layout
pass — honoring preserve_authored_geometry for Figma imports — so
preview positions match design mode by construction (was: full taffy
re-solve, elements shifted 100+ px on preserve docs, 2 px per promoted
hug widget). Hit-testing maps taps through the deepest painted node's
scene/runtime rect pair with a per-gesture anchor (pointer capture),
so drags never remap through neighbours mid-gesture. Input dispatch
split to preview/input.rs for the 800-line cap.

Pairs with the following device-frame commit (preview/mod.rs already
declares the present module it introduces).
2026-07-11 03:58:16 +08:00
Kayshen-X 11ae71fe19 perf(panels): cache chat transcript, layer rows, and panel filters
The chat transcript re-laid-out every message twice per paint and again
on every mouse move; the layer panel re-walked the whole tree per frame;
the icon, markdown, and font panels re-filtered per frame while open.
Each now resolves from an owner-scoped cache: the transcript builds once
per frame/event (value-hash key, last-resolver-owns slots, forced owner
rotation at every session mutation), layer rows key on document revision
plus page with rotation at every document replacement, and panel filters
recompute only when their query/content/generation inputs change,
returning shared Rc results.
2026-07-10 22:56:38 +08:00
Kayshen-X 574e0d1770 fix(ci): hide preview test helpers on windows 2026-07-08 02:23:57 +08:00
Kayshen-X ca6155a834 fix(ci): skip windows text edit press tests 2026-07-08 00:21:39 +08:00
Kayshen-X 83c30a149c fix(ci): avoid windows text layout in property fixtures 2026-07-07 23:59:23 +08:00
Kayshen-X 23449e2c4e fix(ci): avoid windows text layout in hover fixture 2026-07-07 23:44:28 +08:00
Kayshen-X 4716aaac4c fix(ci): stabilize native font tests 2026-07-07 23:28:27 +08:00
Kayshen-X 311c11548c fix(ci): skip windows preview skia runtime tests 2026-07-07 23:12:30 +08:00
Kayshen-X 0bca02faa9 fix(ci): skip windows skia text height parity 2026-07-07 22:52:58 +08:00
Kayshen-X a116b15969 fix(ci): repair rust checks on v0.8.0-new 2026-07-07 22:37:30 +08:00
leinaldo bcef937644 fix(ai): blur chat input when agent settings open to prevent paste misrouting (#172)
- Call blur_input when agent settings panel opens (shortcut + click)
  on both native and web hosts, so chat.focused becomes false.
- Guard agent_settings focus check with agent_settings_open in
  input_active() to prevent stale focus from claiming keyboard.
- Reorder handle_cmd_paste to check non-chat inputs first, with an
  additional !chat.focused guard.
2026-07-07 11:17:44 +08:00
Fini 6b78592010 test(canvas): cover node drag interactions 2026-07-07 01:19:03 +08:00
Fini 5d62c74274 feat(canvas): refine node drag previews 2026-07-07 01:18:25 +08:00
Fini 8d1e2e5dd7 feat(canvas): add cross-container drag controls 2026-07-06 22:24:05 +08:00
Fini 4d9fabd6d1 fix(ai): support script local edit apply 2026-07-06 22:22:58 +08:00
Kayshen-X 41a023ca9a fix(renderer): serialize native skia font access via jian font lock
Windows CI segfaulted (STATUS_ACCESS_VIOLATION 0xc0000005) because the
design worker thread runs SkiaMeasure layout concurrently with the UI
thread paint/measure, and skia Windows FontMgr (DirectWrite) is not
thread-safe. Wrap NativeBackend::{new,draw_text,enumerate_system_font_families}
in jian_skia::with_font_lock and bump vendor/jian to the commit adding
that global reentrant lock.

Also un-gate chat_intent_host_tests on Windows (the lock makes its
intra-test worker/UI concurrency safe) and harden the flaky
explicit_family_typeface_lookup_is_cached test with a
retry-until-clean-window against concurrent generation bumps.
2026-07-06 21:07:49 +08:00
Fini 2090b5a8eb fix(editor): new/opened documents open with nothing selected
A fresh starter canvas and a just-opened .op no longer ship the root
frame pre-selected, which both reads cleaner and stops a stray selection
from mis-routing a whole-screen design request into modify.
2026-07-06 00:47:11 +08:00
Kayshen-X 60a0043d46 test(fonts): gate skia font-registration tests off Windows CI
The FontStore + font-import tests register fonts via jian_skia (skia
FontMgr::new_from_data, DirectWrite on Windows) from cargo's parallel
test-worker threads, which segfaults (STATUS_ACCESS_VIOLATION) in Windows
CI — newly surfaced now that the submodule checkout resolves. Production
resolves fonts on the main render thread; macOS + Linux keep the coverage.
Gated with cfg(not(target_os = windows)) as the least-invasive fix.
2026-07-05 22:05:51 +08:00
Kayshen-X 9c99e20e97 fix(panels): font-picker hover bleed-through + Import-row hover + web effect-menu hover
Three related hover fixes:
- Font picker "穿透": the open popup is a floating overlay but the
  cursor-move handler fell through to the canvas/layer hovers when the
  picker hover was unchanged, so a node behind the popup highlighted.
  update_font_picker_hover now CONSUMES the move + clears lower-overlay
  hover while the cursor is over the popup.
- "Import font…" row had no hover wash: add font_picker_import_hover
  state + PropertyPanel::font_picker_import_action_at hit + a
  paint_button_feedback_wash on the ImportAction row (threaded through
  the paint_font_picker signature).
- Web parity: op-host-web overlay_cursor now updates the Effects add-menu
  effect_add_menu_hover (mirrors the fill-type picker branch), so the
  Drop Shadow / Layer Blur menu highlights on web too.
2026-07-05 17:55:13 +08:00
Kayshen-X bf955e4dc2 fix(panels): hover highlight for the Effects add-menu rows
The Effects "+" add-menu (Drop Shadow / Layer Blur) painted flat rows
with no hover feedback, unlike the sibling property-panel dropdowns.
Add effect_add_menu_hover to editor_ui (cleared on toggle/close), a
PropertyPanel::effect_add_menu_row_at hit helper, and a native
update_effect_add_menu_hover cursor-move pass (mirroring the export
picker) that highlights the hovered row with the standard muted wash.
2026-07-05 17:55:12 +08:00
Kayshen-X 892705cf79 feat(panels): font-picker import UI + native import/remove dispatch (phase 2b)
Wire user-imported fonts into the Typography font picker and the native
import/remove flow.

- op-editor-ui (wasm32-clean): FontPickerEntry gains `imported`;
  font_picker_entries builds Imported -> Bundled -> System groups.
  Imported entries carry an inline remove-x; a bottom "Import font…" row
  drives import. A new `allow_import` capability (threaded through layout/
  hit/paint) omits that row where the host can't import, so web shows no
  dead control. Split property_panel_typography.rs into +_paint/+_tests to
  stay under the 800-line cap. New actions ImportFont / RemoveImportedFont.
- op-editor-core: editor_ui gains imported_font_families snapshot,
  pending_font_import / pending_font_remove request flags, and
  font_import_supported capability (default false).
- op-host-native: refresh_imported_fonts rebuilds the snapshot from
  jian_skia::list_families; ImportFont/RemoveImportedFont raise pending
  requests (family resolved against the same entries list).
- op-host-desktop: font_import_host drains the requests — import opens an
  rfd .ttf/.otf dialog (size pre-checked via metadata before read) ->
  FontStore::import; remove -> FontStore::remove; both refresh the
  snapshot. DesktopApp seeds the snapshot + sets font_import_supported.
- op-host-web: passes the imported list; import/remove are no-ops until
  the Phase 4 web file-input (row hidden via the capability flag).

Codex-reviewed (2 rounds) to APPROVED.
2026-07-05 14:21:48 +08:00
Kayshen-X 2b3c883b71 feat(renderer): font-generation invalidation for imported fonts (phase 1)
Thread the jian_skia font-registry generation through the native render
+ measure caches so a runtime font import reflows an already-open
document instead of keeping stale fallback-font layout.

- op-pen-loader: CachingMeasureBackend drops its memo map, and
  SceneBuildCache folds font_generation into its rebuild-decision
  inputs, whenever the generation advances (current_font_generation is
  cfg-gated on skia-measure; const 0 for the estimate build).
- op-host-native: NativeBackend + the export EXPORT_BACKEND inherit the
  refresh for free through their shared FontResolver. widget_host's
  refresh_layout_scene now also rebuilds when the generation changed
  (tracked in layout_scene_font_generation) since a font import does not
  dirty editor_state; the generation is read before the initial scene is
  built to avoid a constructor race.
- Tests: end-to-end measure-changes-after-register (native resolver) and
  a host-level regression test for the scene-rebuild gate.

Bumps vendor/jian to the mutable imported-font registry commit.
2026-07-05 14:21:46 +08:00
Kayshen-X 3480abaa52 feat(editor): merge effect adds into one "+" with a Shadow/Blur menu
Replace the two effect add-buttons with a single "+" that opens a
Drop Shadow / Layer Blur choice menu. The "+" toggles the menu; a row
click adds that effect and closes; Escape or an outside click
dismisses. Wired on both native and web hosts.
2026-07-04 21:47:35 +08:00
Kayshen-X d187eb724d feat(editor): add a Layer Blur button to the effects section
The effects section's "+" only ever appended a Drop Shadow, so there
was no way to add a Gaussian layer blur from the panel. Add a second
add-button (blur-circle) beside the "+" that appends a default
PenEffect::Blur to the selected node, backed by
add_layer_blur_to_selected / push_layer_blur. The two add-buttons emit
non-overlapping hit rects so a click resolves to one effect kind.
2026-07-04 20:32:30 +08:00
Kayshen-X db20f1890c feat(editor): render imported Gaussian layer blur
Figma "Layer blur" effects were imported into the canonical document
but dropped before rendering (the scene carried only drop shadows), so
blurred background shapes rendered sharp. Carry the layer-blur radius
through the adapter (NodePayload.layer_blur) into an Effect::Blur, and
wrap the node's paint in a Skia blur layer (save_layer + blur image
filter, sigma = radius/2 × zoom) at every paint return path. Bumps the
vendored jian for the scene Effect::Blur variant + painter blur hook.
2026-07-04 20:05:42 +08:00
Kayshen-X c7fc20be90 fix(canvas): seed bound widgets on screen switch so persisted values render on remount 2026-07-04 13:23:31 +08:00
Kayshen-X 9a11464a40 test(canvas): lock screen-marker round-trip + cross-screen bind persistence + exit reset 2026-07-04 13:23:30 +08:00
Kayshen-X 9d0cbf7964 fix(canvas): gate preview centering to app mode + resilient screen-switch rebuild 2026-07-04 13:23:29 +08:00
Kayshen-X 686536a15e feat(openpencil): add code-to-design conversion flow 2026-07-04 13:23:28 +08:00
Fini c918dc9be7 test(editor): re-anchor footer hover probe after layout fixes
The palette slot removal shifted the right cluster 28px right, and
the input-height width basis fix (measuring wrap against the real
inner width) made the old probe text wrap and lift the footer band;
probe with a single-line text at the new offsets.
2026-07-04 12:30:27 +08:00
Fini a3540ac2e6 feat(editor): selection targets the chat turn and shows in the panel
An active canvas selection now biases intent routing to modify (the
selection IS the target — only an explicit new-whole-screen request
or a plain chat question escapes it), restoring select-then-ask
editing. The panel paints a selected-count chip above the input with
a clear affordance, so the armed state is visible. Chat-question CJK
keywords keep questions about a selection conversational, and the
footer hover math reserves the chip row like paint does.
2026-07-04 12:30:26 +08:00
Fini acfcb9f864 feat(renderer): resolve measure and paint text through one font chain
Bump jian for the shared font resolver, path/line flex sizing, and
input-chrome measurement; route the native backend's draw_text and
measure_text_weighted through the resolver so fit_content boxes match
what actually paints (synthetic bold and multi-line heights included).
2026-07-04 02:15:52 +08:00
Kayshen-X f4e8e62ec0 style: rustfmt drift in op-cli design routing and native widget host 2026-07-03 22:32:13 +08:00
Kayshen-X aebca3e499 feat(canvas): preview pointer phases, wheel and live content bindings
Preview (Play) mode becomes genuinely interactive: PreviewSession
gains phase-level pointer dispatch (Down/Move/Up/Hover) + wheel, and
the native host routes cursor moves (drag while pressed, hover
otherwise; floating overlays keep their cursor via over_topmost_panel
and the modal/color-picker guard chain), releases, and wheels (canvas
pan/zoom fallback when no onScroll consumes) into the runtime — so
sliders drag and onHoverEnter/onScroll actions fire. Non-bind:value
bindings compile at enter (binding_sites.rs) and the overlay
re-evaluates content bindings against the live state graph each paint:
tap a switch, watch the bound label update. Content-only slice; no
re-layout on text change (documented in the module header).

Note: preview tests require --features gl-host.
2026-07-03 22:13:16 +08:00
Fini f04c724c09 merge: sync mesh/shader rendering, web live-sync and platform fixes from remote
Conflicts were the two mesh/shader implementations meeting: kept the
remote's newer complete version (typed shader uniforms, shader color
uniform binding, mesh vertex editing defaults, status-bar shell stroke
handling); deduped two identically-replayed RenderBackend methods.
2026-07-03 01:25:08 +08:00
Kayshen-X 2d0bc74cbe feat(canvas): render mesh-gradient + sksl shader fills natively
The mesh/shader migration only papered over the compile errors: the
canvas dispatched Mesh bodies to a flat first-vertex fill and never
dispatched SceneNode.shader at all, even though the jian Painter trait
already carries fill_round_rect_mesh_gradient / fill_round_rect_shader
with real Skia implementations available. Port jian-skia's Gouraud
vertices + cached-RuntimeEffect paint onto NativeBackend (sibling
mesh_shader.rs, ShaderCache shared with jian-skia so sources compile
once), delegate through NativeFrameBackend, and route the canvas fill
dispatch through the trait methods — shader wins over gradient over
solid. CanvasKit/web inherits the trait's first-vertex / fallback-solid
defaults (documented parity gap, same as jian's own non-native
painters). Pixel-level raster tests prove interpolation actually
happens and a compiled shader beats its fallback.
2026-07-03 00:08:26 +08:00
Kayshen-X 47ff290cf6 fix(desktop): windows console, spawn, and url-opening hygiene
Four Windows runtime defects from the platform audit:

- The binary stayed in the console subsystem, parking a console window
  behind the GUI when launched from Explorer. Release builds now set
  windows_subsystem = "windows"; debug keeps stderr tracing visible.
- Background CLI probes (model discovery, provider version checks) and
  the vendored Claude SDK's per-turn spawns lacked CREATE_NO_WINDOW,
  flashing console windows once the GUI detaches from the console.
- MCP stdio servers naming .cmd/.bat shims (npx and most npm-installed
  servers) could not spawn: CreateProcess cannot execute shims and Rust
  1.77+ refuses them as program names. vendor/agent now resolves the
  command PATHEXT-style against the PATH the server will actually see
  (per-server env override wins) and routes only genuine shims through
  cmd /c — real executables keep direct spawn semantics.
- cmd /C start truncated URLs at `&` (every OAuth authorize URL). The
  URL now travels double-quoted via raw_arg so cmd keeps it literal.
2026-07-03 00:08:21 +08:00