Commit graph

151 commits

Author SHA1 Message Date
Kayshen-X d5b0088cfb fix(canvas): drop an image onto an empty placeholder fills it again
A jian bump made unpainted container bodies opt out of the click
hit-test, which also excluded empty placeholder boxes from image-drop
resolution — a drop over one inserted a fresh node instead of filling
it. Resolve the drop target through the new fill hit-test variant that
keeps empty bodies in the path, and bump the jian pin to carry it.
2026-08-09 22:54:21 +08:00
Fini 3a8c154739 fix(renderer): keep transparent decoration layers out of the way of clicks
Two carousel templates stack a full-bleed transparent shell (washi tape,
punch holes, film sprockets) in front of their content. Every click over
the board landed on that shell instead of the text behind it.

The engine half is the jian bump: a container that paints nothing no
longer claims its own body in the hit-test. The template half marks
those shells `locked`, so the shell is unselectable as a layer while the
marks inside it stay individually selectable — dragging a piece of tape
no longer drags the whole layer, and a marquee no longer nets the board.

Both templates are regenerated output, so they also carry the row wrap
their generators gained in the same pass.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:53:09 +08:00
Fini 95576d7af3 fix(renderer): hit only the active tabs panel and drop unpainted widget strokes
jian-scene now exposes visible_children() as the single judgment both
paint and hit-test share, so canvas clicks land on the tabs panel the
user sees instead of a hidden sibling occupying the same grid cell
(vendor/jian bump carries the walk change, rebased onto the thumb
registry work). A stroke that declares thickness but resolves no
paint no longer masquerades as opaque black on widget tracks and
borders — three unpaintable shapes drop it and the role defaults take
over, while $ref-driven and ordinary shape strokes keep their
historical behaviour. Zero-thickness tab bar strokes stop painting
skia hairlines.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:24:43 +08:00
Kayshen-X f3ea6e8c99 fix(web): make the identity reset claims real and cover /mcp in the write barrier
Re-audit round: an aborted edit script had silently dropped half a
prior fix, so each item here is grep-verified in the report trail.

- identity reset now actually drains the credential sync queue and
  epoch-tags in-flight callbacks so account A's completing upload
  cannot become account B's state; an empty partition resets every
  account-scoped setting (locale, recents, agent settings, theme)
  instead of inheriting the previous account's; rebuilt persistence
  baselines go through the CredentialLoad seams so settings keep
  saving and unsupported snapshots stay fail-closed after a switch
- mutation admission is one helper shared by the REST push and /mcp:
  write tools take a WritePass (read tools stay free) and refuse with
  a proper tools/call error once shutdown closes the barrier; the
  write drain escalates to a 60s hard cap with blocked-writer logging
  instead of flushing past active writers at 10s
- a runtime-rejected ingest restores the thumbnail registry snapshot
  captured before install (vendor/jian gains the restore_snapshot
  counterpart), so a rejected document's thumbnails no longer stay
  active locally
2026-08-08 21:35:27 +08:00
Fini 63198ac4f8 feat(renderer): carry interactive widget semantics through scenes 2026-08-07 02:00:23 +08:00
Fini 599ab11f6c fix(desktop): update casement for direct IME commits 2026-08-07 02:00:22 +08:00
Fini 97b8f089a2 fix(canvas): adopt stack semantics for layout:none and retire the flow bandaid
Bump jian for the single-cell stack compilation of layout:none frames.
The loader's repair_inferred_horizontal_container previously rearranged
explicit layout:none children into a row to paper over the engine gap;
with the engine fixed that bandaid would shove correctly stacked layers
out of the frame, so it now only fires for documents that omit the
layout field entirely. Corpus-wide differential over 184 documents:
60 of 240k node rects change, all overlay repairs (hero scrims and
avatars regaining their parent box), none collapsing to zero.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-07-28 22:11:59 +08:00
Kayshen-X 70e202d802 feat(editor): improve document workflows and rendering 2026-07-23 21:15:04 +08:00
Kayshen-X 01820bf5ea fix(editor): improve import and generation reliability
Expand HTML/CSS import fidelity, preserve layered fills and accurate fonts across HTML, Figma, and OP files, and make missing-font resolution selectable and case-insensitive. Harden code generation recovery and preview rendering, make model selection immediate, and keep image decode/compositing consistent across native and web hosts. Add mobile dependency and widget-boundary guards so all supported targets retain the intended feature surface.
2026-07-21 06:34:53 +08:00
Kayshen-X 4bda4cb0e9 feat(renderer): even-odd fill, per-corner radii, background blur end-to-end
Vector plan Tasks 3-5: winding rule imports onto PathNode.fillRule and
reaches skia/CanvasKit fill types through new *_with_fill_rule painter
entry points; per-corner radii flow payload->scene->RRect on both
backends; BACKGROUND_BLUR renders via backdrop save layers (native
SaveLayerRec backdrop, CanvasKit saveLayer backdrop). Includes jian
pointer bump (6bed2be). no-verify: repo fmt gate trips on unrelated
in-progress op-html sources.
2026-07-19 20:45:02 +08:00
Fini 0431313804 fix(renderer): pull jian's null-family font fallback fix
Bumps the vendored pin to merge/responsive-m1a-into-main @ fd17e40, which
names a default fallback family so Skia's font manager never builds an
SkString from a null family pointer.
2026-07-19 19:30:26 +08:00
Fini 4162006fab feat(renderer): adopt responsive-capable jian and adapt hosts
Merge Kayshen's feat/responsive-m1a (schema 1.2 + responsive opt-in,
breakpoint ranges, variant tables, transactional variant swap) plus
main's fill-rule/corner-radii/background-blur into the vendored pin
(merge/responsive-m1a-into-main @ 42772b6). Responsive stays double-
gated opt-in, so every existing document renders unchanged.

Adaptation is mostly new optional fields defaulted to None/false. The
one real behavior interaction: jian's node_rect now bakes a root's
authored origin into its returned rect (root_origins, added for the
responsive multi-root canvas), which doubled offsets at the three
places that added the origin themselves — the loader's layout harvest,
App Mode tap solving, and the preview's scene-to-runtime-space mapping
(the last now subtracts the root origin back out, gated on jian's own
is_origin_normalized so responsive roots stay untouched).
2026-07-19 02:27:09 +08:00
Kayshen-X 23467901d4 chore(deps): bump jian for image table dedup and Arc scene payloads
jian bfd6ca5 (deduplicated .op image table + shared-Arc load) and
633b9a0 (SceneNode image payloads as Arc<str>). no-verify: the repo
fmt gate currently trips on unrelated in-progress op-html sources.
2026-07-18 13:23:38 +08:00
Kayshen-X 6721a02837 chore: update Jian submodule 2026-07-15 21:17:21 +08:00
Fini 44a4fe4e24 feat(editor): align canvas layout and sizing behavior 2026-07-14 00:29:58 +08:00
Fini c5feaeec01 fix(desktop): open a multi-page file where its design actually is
A 16-page document whose first page was a blank cover opened onto empty
canvas ("content bbox None") and read as a load failure - the design was
on page 2. With no editor metadata to restore, the file now lands on the
first page that has content; a file that is empty everywhere still opens
on page 1.
2026-07-14 00:29:51 +08:00
Kayshen-X a2c26855df chore(agent): update vendor agent submodule 2026-07-12 14:39:30 +08:00
Fini b61a859396 chore(vendor): bump agent runtime submodule 2026-07-12 02:56:43 +08:00
Kayshen-X d866690cc4 chore(agent): bump vendor/agent to 2ab5e97
Picks up the multilingual product-overview README (docs-only); pointer
target is on the agent repo's origin/main.
2026-07-11 09:46:28 +08:00
Kayshen-X da7fa17a78 chore(agent): bump vendor/agent to 5c0f950 (match-centered grep cap)
Advance past c8fb619b (head/tail grep cap that could hide a match in a
long line) to origin/main, which centers the per-line cap on the match
plus repairs feature gates/advisories.
2026-07-07 21:44:12 +08:00
Kayshen-X 8e9f1c5f1e chore(agent): bump vendor/agent to c8fb619b (rebased onto main + grep cap)
Advance the submodule past the pre-rebase 876c890 (no longer on any
remote branch) to origin/main, which carries the ripgrep/compression
series rebased onto main plus the grep per-line output cap.
2026-07-07 21:33:18 +08:00
Kayshen-X 08b5be80b4 chore(agent): bump vendor/agent to 876c890
Advance the vendored agent runtime submodule to origin/main
(876c890 'feat(shell): apply output compression before the Bash cap').
2026-07-06 22:13:02 +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
Kayshen-X 0611cc9714 chore(vendor): bump jian for the empty-source FontMgr fix (Windows CI segfault) 2026-07-05 22:24:31 +08:00
Kayshen-X 54b626759e chore(vendor): bump agent submodule to ea27441 (origin/main)
Update the vendor/agent pointer from a6f1897 to the current agent-rs
origin/main tip (ea27441). Verified: cargo check --workspace builds clean
against it; ea27441 is on origin so submodule checkout resolves in CI.
2026-07-05 19:41:04 +08:00
Kayshen-X d48ef56624 chore(vendor): bump jian for the widgets MSRV clippy fix 2026-07-05 19:26:11 +08:00
Kayshen-X 53da54ab8b fix(desktop): persist imported font before mutating the live registry
FontStore::import registered the font in the process-global registry
(bumping the generation) BEFORE writing it to disk. A failed
create_dir_all/write/save_index then left the font live + rendered this
session but unpersisted — while the caller reported the import as failed
and popped an error dialog. Reorder to parse (no registry mutation via
the new jian_skia::parse_imported_font_meta) -> persist to disk ->
register last, so the live registry is only mutated once persistence is
durable. New test failed_persist_does_not_leak_into_the_live_registry
(file-rooted store forces a disk failure) pins it.

Bumps vendor/jian to the parse_imported_font_meta commit.
2026-07-05 14:21:49 +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 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 04e3cbcc69 chore(jian): update submodule to rebased main 2026-07-04 13:31:12 +08:00
Kayshen-X 686536a15e feat(openpencil): add code-to-design conversion flow 2026-07-04 13:23:28 +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 ccc0297261 chore(agent): bump vendored agent runtime to upstream main 2026-07-03 22:18:08 +08:00
Kayshen-X ae11d8894a feat(ai): teach and promote radio-group, number-input and progress roles
Bumps vendor/jian to feat/promote-widen (single-source ROLE_TABLE,
9 promotable kinds, promotable_roles() export). The jian-components
generation skill teaches the five new role strings, and the loader's
role-vocabulary test now iterates promotable_roles() directly — the
skill can never silently drift from the promote table again
(jian-ops-schema joins op-ai-skills as a dev-dependency for that
test). Tabs is documented as deliberately not promotable.
2026-07-03 22:15:35 +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 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
Kayshen-X 061296c779 feat(render): handle mesh-gradient + shader fills across editor consumers
vendor/jian ff39a41 added PenFill::MeshGradient / PenFill::Shader plus
SceneGradient::Mesh and SceneNode.shader; every exhaustive match over
those types stopped compiling. Cover them with faithful conservative
semantics: opacity getters/setters treat the new bodies like their
gradient siblings, variable binding walks mesh vertex stops (shader
uniforms are untyped — left alone), canvas paint degrades mesh to its
first vertex colour until the RenderBackend grows a mesh method, the
property panel reports the neutral Solid type (no lossy conversion
offered), and lint contrast checks skip both like image fills.
2026-07-03 00:08:19 +08:00
Kayshen-X 0ba60361d4 fix(canvas): don't arm an erase frame when a design run finishes empty
finish_if_epoch previously routed the empty-queue case through
drain_finished_run, which set the process-global needs_final_frame flag
even though a run that never queued a reveal never put a cursor on
screen. That stray flag made next_reveal_deadline_ms return a redraw
deadline out of an idle registry, and — because the registry is shared
across the whole test binary — perturbed an unrelated exact
animation-deadline assertion whenever a design-session test dropped an
empty session in parallel.

Clear an empty finish inline without arming the flag; the paint-path
drain still arms it after real reveals prune, where a cursor genuinely
was on screen. Adds a regression test.

Verification note: the op-editor-core suite could not be run for this
commit because the shared workspace is transiently non-compiling under a
concurrent mesh-gradient/SkSL-shader jian bump (new PenFill variants not
yet handled in fills.rs — unrelated files). Change is trace-verified and
touches only agent_indicators; re-run pending the tree compiling again.
2026-07-03 00:08:18 +08:00
Fini 1e3fdc91e1 chore(deps): bump jian for main-axis fill_container grow
Points the jian submodule at the layout fix that grows a main-axis
fill_container child (a sidebar footer spacer) into the column remainder.
2026-07-02 21:21:52 +08:00
Fini 0730e1ccce feat(host): headless render-parity tooling — bundled fonts, margin env, layout dump
Bundle 11 OFL design-font TTFs and register them with jian-skia at startup so
--render-shots reproduces Pencil's glyph metrics without system fonts.
OPENPENCIL_RENDER_MARGIN tightens the export crop to match Pencil export_nodes
(no frame), and OPENPENCIL_DUMP_LAYOUT prints every node's computed rect as JSONL
to diff our layout against Pencil snapshot_layout element-for-element. Bumps
vendor/jian to the bundled-font + Auto=no-wrap render-parity commits.
2026-07-02 21:21:30 +08:00
Fini bf4a741d8e feat(loader): mesh/shader fills + .pen→.op legacy normalize hardening + pen2op bin + design-library merge
Bumps jian submodule for mesh-gradient/shader PenFill variants. normalize_legacy_doc
gains 8 .pen-compat repairs (stroke-fill string, gradient fill, icon/prompt nodes,
$ref cornerRadius, single-object fill). New pen2op bin converts Pencil .pen→.op.
library.rs merges a .lib.op's reusable masters+variables into a working doc.
2026-07-02 21:21:09 +08:00
Kayshen-X fc58a4601d chore: bump jian submodule (node promotion for batch_design) 2026-07-02 21:21:07 +08:00
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00
Kayshen-X d603f2f388 chore: bump jian submodule (node promotion for batch_design) 2026-06-25 00:15:55 +08:00
Fini d353a58450 chore: bump jian submodule (image cover + leaf flex-shrink) 2026-06-21 23:18:22 +08:00
Kayshen-X ec5ba9c283 fix(editor): repair strokes and legacy render layout 2026-06-21 16:52:31 +08:00
Kayshen-X ba6aae07dc perf(canvas): Arc-share image src/url to make scene-cache O(1)
Bump vendor/jian to the ImageSrc(Arc<str>) change: ImageNode.src and
ImageFillBody.url are now reference-counted, so the per-frame
SceneBuildCache document clone + content-compare settle in O(1) for an
unchanged multi-MB image source instead of walking the whole base64
payload. Completes the deeper half of the image-drag perf fix (the
insert-time down-scale landed in a084628f).

Consumer sites updated for the newtype: writes/constructs use .into();
sites assigning into a String field use .to_string(); reads are
unchanged via Deref. Also fixes a stale gl-host test
(remote_icon_insert_bakes_svg_d_as_path_node) that still assumed icon
insert appends to the end — it now locates the baked path by icon_id and
asserts it sits above the selection (the insert-above behavior).
2026-06-21 10:01:37 +08:00
Kayshen-X eed21064b9 feat(canvas): insert icons/images above the selected layer
Icon and image inserts append()ed to the page root (the back / bottom of
the LayerPanel), ignoring the selection. Add insert_node_above_selection:
the new node lands in the selected node's parent at the selection's index
— one row up in the panel, one step toward the front in z-order (canvas
paints children back-to-front). Falls back to page-root append when
nothing is selected.

Bumps vendor/jian to d50a715 (O(1) image source id — fixes image-drag lag).
2026-06-20 23:58:58 +08:00
Kayshen-X 9b77f170c8 refactor(git): render masked HTTPS credential via unified TextInputView
Route the Remotes-section HTTPS credential field through the unified
TextInputView with mask: Some(&mask_credential) (new
paint_text_input_view_masked helper), deleting the ~90-line hand-rolled
masked renderer + masked_byte_for_source_byte caret remap in
git_panel_remotes. The token still shows as user:•••• with a correct,
family-aware caret. Other TextInputView call sites pass mask: None.

Bumps vendor/jian to c451e16 (TextInputView::mask).
2026-06-20 22:26:28 +08:00
Kayshen-X 11431e1a18 refactor(store): reuse jian_core::color, drop op-editor-core color_convert
The colour maths now lives once in jian_core::color; op-editor-core
re-exports it through color_picker (so crate::color_picker::parse_hex_rgb
and op_editor_core::* paths are unchanged) and deletes its duplicate
color_convert module. Bumps vendor/jian to 3404ad8.
2026-06-20 21:41:17 +08:00