Commit graph

1889 commits

Author SHA1 Message Date
Kayshen-X 22da4eed3c chore(repo): retire agent-native; root is now a pure Cargo workspace
- Remove the Zig `agent-native` git submodule (no Rust crate depends on it;
  the built-in agent runtime is the Rust `agent` crate under vendor/agent).
- Make the repo root a pure Cargo workspace: delete root package.json + bun.lock;
  move all web-SDK JS/Bun tooling into packages/ (new packages/package.json
  workspace root, generate-iconify-catalog.mjs, and the oxlint/oxfmt configs).
- Scrub agent-native from all docs and fix README dev commands + prerequisites
  to reflect the Rust product (cargo directly; bun tooling runs from packages/).
- Drop the deleted-root-package.json read from the op-host-web ci_workflow test.
2026-07-01 22:16:04 +08:00
Kayshen-X 77dca8c01b chore: retire TypeScript OpenPencil, delete apps/* + pen-* packages
The product is now Rust (crates/) + the Zig agent runtime
(packages/agent-native) + the wasm-backed web SDK (packages/op-web-sdk*).
Delete the retired TypeScript editor/desktop/CLI (apps/*), the pen-*
packages, and their orphaned TS tooling, Dockerfile, and root tsconfig.

Rust-side hooks into the deleted TS are rehomed so the workspace still
builds, tests, and releases green:
- op-mcp: vendor the 10 element-tool-defs shards it include_str!'d from
  pen-mcp into crates/op-mcp/assets/element-tool-defs/ (git renames), and
  repoint the compile-time includes + the runtime parity test's read_dir.
- op-orchestrator: reword the parity panic that named the retired
  dump-planner-golden.ts generator (goldens stay as frozen baselines).
- CI: drop the TS-derive golden-drift + planner-parity oracle jobs and the
  TS path filters from rust-check.yml; repoint the cargo-bundle icon in
  rust-release.yml to crates/op-host-desktop/assets/icon.png.
- Release (Windows): recover the deleted apps/desktop/build/icon.ico into
  crates/op-host-desktop/assets/icon.ico and repoint the NSIS installer
  (rust-release.yml /DICON_FILE + package-windows.nsi ICON_FILE fallback +
  its example invocation), so the Windows release build still finds it.
- Remove the dead .githooks/post-commit hook — it rebuilt the deleted
  out/mcp-server.cjs via the removed mcp:compile script and only watched
  now-deleted pen-* paths; the Rust MCP is built by cargo.
- Recover apps/web/public/logo-discord.svg to screenshot/ and repoint the
  15 READMEs' logo/link paths.

package.json is now Rust-first (dev/build/test -> cargo + serve-web) and
docs (CLAUDE.md/AGENTS.md/packages+crates CLAUDE.md) describe the Rust
product. The retired TS remains recoverable via git (last tag v0.7.5).

Verified: cargo check --workspace green; cargo test -p op-mcp 353 passed;
cargo test -p op-orchestrator 848 passed; cargo test --workspace --no-run
compiles all targets; rust-release.yml parses and references no deleted
build assets.

Pre-commit fmt hook bypassed: its cargo fmt --check is pre-red from a
repo-wide stable-vs-nightly rustfmt config drift affecting unrelated files;
the .rs files touched here produce no fmt diff.
2026-07-01 21:12:39 +08:00
Kayshen-X a4ee78ba86 fix(ai): chat panel hover/close/send-slot refinements (#41/#42/#43)
- #41: collapse chevron renders through IconButton so it shows the ghost
  button-hover wash instead of only swapping the icon color
- #42: send/stop share one circle slot (toggle in place); footer cluster
  sits snug with no reserved stop gap; hit-test routes streaming->stop else send
- #43: example cards stay clickable without a connected model (clicking one
  fills the input; sending still requires a model)
- tab close glyph shows only while the tab is hovered (inset still reserved
  on the active tab so the title doesn't reflow)
2026-06-25 22:40:48 +08:00
Kayshen-X d603f2f388 chore: bump jian submodule (node promotion for batch_design) 2026-06-25 00:15:55 +08:00
Kayshen-X b1aebc3252 feat(ai): 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-06-25 00:12:45 +08:00
Fini 541da7bcb7 test: adapt cleanup + chat-session tests to origin's rebase
op-orchestrator:
- import EffectField directly (origin's cleanup.rs no longer re-exports it
  via `use super::*`)
- insert both concurrent-cleanup roots in one InsertSubtree, since origin's
  command_root_replace now replaces an empty root on single-node top-level
  inserts (two sequential empty inserts would collapse to one child)
- defer the bottom-nav upward-shadow assertion pending reconciliation with
  Kayshen's e3ed2f1e "normalize mobile bottom tabs" (top-navbar transparency
  assertion retained)

op-host-desktop:
- import clear_fresh_starter_frame_for_design via `super::launch::` so the
  retained starter-frame tests resolve it after the merge
2026-06-21 23:18:23 +08:00
Fini d353a58450 chore: bump jian submodule (image cover + leaf flex-shrink) 2026-06-21 23:18:22 +08:00
Fini d517c0a2c0 chore(ai): smoke concurrency override + ab-v9 matrix 2026-06-21 23:18:21 +08:00
Fini ea3f94093c fix(editor): desktop chat session + builtin http handling 2026-06-21 23:18:20 +08:00
Fini df57392a1a fix(canvas): avoid double-applying text position in draw_text 2026-06-21 23:18:19 +08:00
Fini f1ceafb06d fix(ai): tighten mobile overflow / jsonl / decomposition skills 2026-06-21 23:18:18 +08:00
Fini 4ac5b5fa69 fix(lint): guard active nav pill against sibling corner-radius flattening
A 100%-rounded active tab nested in a nav pill was flattened to a sharp
square by the sibling corner-radius consistency detectors. Skip the fix when
a group's radius is pill-scale (>= 40) so the active pill survives validation.
2026-06-21 23:18:17 +08:00
Fini 169e6abed2 feat(orchestrator): tree-heuristics, palette-harmonize, post-pass refinements
Port the post-streaming tree-heuristics pass to Rust (tree_heuristics.rs):
- fill_card_leading_image_width: a card's leading header image spans the
  card width instead of leaving a white gap (160px image in a 252px card)
- fix_notification_badge_overlay: a badge authored as a flex sibling of an
  icon becomes a corner-overlay circle, not a square beside the icon
- active nav-tab pill, card image corner clip, invisible text-band fill,
  stacked-overlay-to-absolute, redundant colored wrapper strip

Add palette_harmonize.rs: retint the cool-slate neutral ramp toward the
page's temperature so a warm design no longer reads as off-palette gray.

Plus role-resolver / cleanup / scaffold / run refinements accumulated from
the glm-opus design-parity campaign.
2026-06-21 23:18:16 +08:00
Kayshen-X c4a9d8b3c6 fix(ai): harden style guide isolation 2026-06-21 22:45:32 +08:00
Kayshen-X 5de9bb9734 fix(ai): improve provider design generation prompts 2026-06-21 22:23:26 +08:00
Kayshen-X 53f0a3121d fix(ai): keep a bare ellipsis when ellipsize space is sub-glyph
ellipsize_to_width returned an empty string when not even one glyph
plus the ellipsis fit, dropping the only usable truncation marker. Keep
the bare `…` (matching the per-module ellipsize helpers) so the
truncation stays visible. Updates the unit test accordingly.
2026-06-21 22:08:52 +08:00
Kayshen-X e3c487bf75 fix(ai): ellipsize the collapsed chat pill title
The collapsed AI-chat pill is a fixed 150px wide, but its title was
drawn untruncated, so a long chat title (e.g. the design prompt)
overflowed past the chevron and the pill's right edge. Ellipsize the
title to the space between the bubble icon and the chevron. Adds a
shared width-aware util::ellipsize_to_width (measure-closure based, so
it stays decoupled from PaintCx) + a unit test. Covers native + web
(shared widget).
2026-06-21 21:56:46 +08:00
Kayshen-X 3e4286c332 fix(ai): improve chat-generated document handling 2026-06-21 21:26:34 +08:00
Kayshen-X 992947539b fix(orchestrator): normalize mobile bottom tabs 2026-06-21 21:23:15 +08:00
Kayshen-X 84a668611f fix(orchestrator): strip misrolled mobile search wrappers 2026-06-21 21:08:00 +08:00
Kayshen-X 4c113b2d4a test(panels): move fill-ref removal test to fills_tests
Keep command_tests.rs under the 800-line file limit by relocating the
remove_selected_fill_clears_the_variable_ref test to fills_tests.rs,
where the other fill mutator tests live.
2026-06-21 19:57:41 +08:00
Kayshen-X 6068a3db72 fix(panels): clear fill variable binding when removing a fill
A fill bound to a colour variable keeps a node->variable entry in
ui.variables.fill_refs. The scene resolver's fill_for lets a registered
fill ref WIN over container.fill, so removing the fill row cleared
container.fill (panel goes empty) but left the dangling fill_ref — the
variable colour kept painting. "Deleted the fill but the colour stays"
on token-based (old .op) designs. remove_selected_fill now drops the
fill_refs entry for the primary fill too. In-memory only; old .op files
load unchanged. Adds a core unit test + 4 host scene-clearing tests
(plain / nested / multi-fill / variable-bound end-to-end).
2026-06-21 19:51:52 +08:00
Kayshen-X c697a3036a fix(orchestrator): strip mobile chrome shells 2026-06-21 19:31:18 +08:00
Kayshen-X 387385fc67 fix(orchestrator): resolve scaffold root after replacement 2026-06-21 19:00:50 +08:00
Kayshen-X 8049cd5144 fix(orchestrator): tighten mobile generation rhythm 2026-06-21 18:47:48 +08:00
Kayshen-X 953749cfee fix(panels): stroke-width seed matches the painted value
Clicking the inline stroke-width input on a node with no stroke seeded
"1" (auto-filling a width the user never typed), and the round() seed
snapped fractional widths like 2.5 to "3". Seed via the same
format_panel_number(snapshot width, 0 when unset) the input paints, so
focusing never changes the displayed value. Fixed on native + web; adds
2 seed regression tests.
2026-06-21 18:26:11 +08:00
Kayshen-X efb4b84e7d fix(orchestrator): refine chat-generated mobile layouts 2026-06-21 18:24:12 +08:00
Kayshen-X 7826163240 fix(panels): show colorless stroke width in property snapshot
cmd_set_node_stroke_width attaches a fresh stroke with fill: None
(width, no color). The panel snapshot gated its `stroke` field on a
parseable solid color, so a colorless stroke's width vanished from the
panel: the width input read back 0 on blur even though the model kept
the width. Surface the stroke whenever it has a width, using the slate
placeholder color when no solid color is set. Adds 4 host tests
(commit-from-nothing, click/type/blur, snapshot repro).
2026-06-21 18:15:52 +08:00
Kayshen-X 00a4616b11 feat(panels): multi-fill + stroke-mode editor, property-panel polish
Batch commit of the property-panel work: multi-fill Fill editor, stroke
edit modes (uniform/per-side), clip support, plus polish — every
property-panel icon centred in its hover wash (fill X, padding/stroke
gears, gradient/effect remove), justify-row + size washes fit-content,
stroke duplicate width removed (inline width Single-only, per-side grid
otherwise), Openverse register link fixed + hover, ImageSrc Arc bump.

Known issue: stroke-width edit does not persist on a node whose
node_stroke_slot is None (see cmd_set_node_stroke_width) — follow-up.
2026-06-21 17:24:42 +08:00
Kayshen-X ec5ba9c283 fix(editor): repair strokes and legacy render layout 2026-06-21 16:52:31 +08:00
Kayshen-X 1b7664cbfc feat(panels): hover underline on the Openverse register link
The register link had no hover affordance. Track a
hover_image_search_register_link flag (set from the OpenImageRegisterLink
hit in both host cursor sweeps) and underline the link + arrow on hover.
2026-06-21 13:49:20 +08:00
Kayshen-X 457a67af38 fix(panels): point web Openverse register link at the API docs
Same 405 fix as the native host (8bba6345) — the web path still opened
the POST-only auth_tokens/register/ endpoint.
2026-06-21 12:43:39 +08:00
Kayshen-X bcc50eefde fix(panels): point Openverse register link at the API docs
The link opened the raw auth_tokens/register/ endpoint, which only
accepts POST — a browser GET lands on a 405 page. Point it at the API
reference's auth section, which documents how to register for credentials.
2026-06-21 12:38:46 +08:00
Kayshen-X d37e85960b fix(canvas): convert relinked image src to ImageSrc
The relink-image handler assigned a String to ImageNode.src, which is
now ImageSrc after the Arc-share change (ee0a933e) — this conversion was
applied in the working tree but missed from that commit, leaving
op-host-desktop compile-broken at the committed state. Add the .into().
2026-06-21 10:21:25 +08:00
Kayshen-X e2ca2f03de feat(panels): per-fill multi-fill Fill editor (stack/add/delete fills)
The Fill section was single-fill (header "+" only reset one solid, row "×"
cleared but a default row re-rendered). Now it's a Figma-style stacked list:
"+" appends a fill, each fill renders its own head row (swatch + type
dropdown + opacity% + ×), and "×" removes that specific fill. Built on the
indexed model ops from 17310556; mirrors the Effects multi-item pattern.

- snapshot: NodeSnapshot.fills: Vec<FillSummary>; legacy fields derived from
  fills[0]; walkers thread fills: &[FillSummary] (VisibleSections stays Copy).
- actions: RemoveFill(usize), SetFillType{index,fill_type},
  ToggleFillTypePicker(usize), OpenFillColorPicker(usize).
- state: PropertyFocus::FillHex/FillOpacity carry a fill index;
  ColorPickerState.fill_index indexes the HSV writeback (ColorTarget unchanged).
- paint/hit/height loop per fill in lockstep (paint_one_fill,
  push_fill_action_rects, fills_section_height); both hosts dispatch by index.

Backward compatible (fill is already Vec<PenFill>; old single-fill .op loads
as one row). Non-primary gradient/image fills are head-row-only for now
(editable gradient/image body stays primary-fill-only to avoid the ColorTarget
ripple); solid color/opacity/type are fully per-index.
2026-06-21 10:11:50 +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 a922bd2d0b feat(editor): open the Openverse register link from Settings → Images
The "Register at Openverse" link in Settings → Images → Advanced was painted
but inert. Add a hit-test (register_link_rect → ImagesHit::OpenRegisterLink →
AgentSettingsHit::OpenImageRegisterLink) and open
https://api.openverse.org/v1/auth_tokens/register/ on click — default browser
on desktop (open/xdg-open/start) and a new tab on web (window.open).
2026-06-21 09:03:09 +08:00
Kayshen-X 881d9356d3 feat(variables): indexed multi-fill model ops + selected-node mutators
Foundation for a per-fill Fill editor (stack multiple fills, add/remove by
row). Additive, backward-compatible (fill is already Vec<PenFill>; old .op
files load as a single fill):
- fills.rs: fill_type_of, fill_count, fill_type_at, add_fill,
  remove_fill(i), set_fill_type_at(i), set_fill_hex_at(i),
  set_fill_opacity_at(i).
- property_edit_mutators.rs: add_selected_fill, remove_selected_fill(i),
  set_selected_fill_type_at(i), set_selected_fill_hex_at(i),
  set_selected_fill_opacity_at(i) (via a with_selected_node helper).

UI/host wiring lands separately.
2026-06-21 09:01:21 +08:00
Kayshen-X f2f50e36cf perf(canvas): down-scale oversized images on insert to fix drag lag
A multi-megabyte base64 image src lives inline in the document's
PenNode::Image.src, so every scene rebuild clones + compares it and the
canvas decodes a full-resolution bitmap — moving a 5.4MB image lagged.

Shrink an oversized raster source the moment it enters the document, at
every host path that produces an inline data: URL into the doc src:
file-pick import / fill-image / relink (persistence_image), fetched +
searched images (image_search_session), and inline-base64 AI generations
(image_generate_host). New image_downscale module decodes via skia, fits
the longest edge to 2048px (or re-encodes a >2MB source at native size),
and emits JPEG for opaque sources / PNG for alpha — adopting the result
only when it actually shrinks. Animated GIF / WebP are detected by magic
bytes and left whole so re-encoding never drops frames.
2026-06-21 02:43:57 +08:00
Kayshen-X 61bec3db2b fix(canvas): keep insert-above at the page root for nested selections
insert_node_above_selection inserted into the selection's immediate parent,
which broke a nested selection: a free-positioned icon/image dropped into a
flex/auto-layout frame gets reflowed away from the cursor (and can detach
the selected flow child), and into a clipped frame it can be hidden. Insert
at the page root above the selection's top-level ancestor (descendant_contains)
instead — correct for top-level selections, safe for nested ones.
2026-06-21 00:08:54 +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 9613edcde0 chore(web): snapshot in-progress web-host work (settings module + deps)
Commits the concurrent session's remaining uncommitted changes at the
user's request: op-host-web web_settings.rs (new module) + canvaskit / lib
wiring + Cargo.toml / bun.lock dependency updates, plus minor canvas-overlay
and host-test tweaks.

Verified to compile before committing: op-editor-ui + op-host-native (native)
and op-host-web (wasm32) all `cargo check` clean.
2026-06-20 23:44:25 +08:00
Kayshen-X 63a70f38e9 fix(web): drop web_settings refs swept into the icon-scroll commit
fe9983a0 committed crates/op-host-web/src/widget_host.rs wholesale, which
swept in a concurrent actor's in-progress web_settings integration (the
settings_fingerprint field + load_into/fingerprint/save_if_changed calls)
WITHOUT its module (web_settings.rs untracked, the mod decl + serde dep
uncommitted) — so clean HEAD referenced a missing module. Restore
widget_host.rs to the pre-sweep base and re-apply only the intended
icon-picker scroll wiring; the actor's web_settings WIP stays uncommitted.
2026-06-20 23:38:12 +08:00
Kayshen-X 88cc8e94d8 fix(variables): clamp variant-header hover wash to its hit column
A long variant name could make the fit-content variant-header hover wash
wider than its column and bleed into the neighbouring variant column. Clamp
the wash right edge to the hit column (`col_w.min(176)`), matching the
size-checkbox / footer clamps. Wash regression tests moved to a sibling
variables_panel/wash_tests.rs (keeps tests.rs under the 800-line cap) with a
new long-variant clamp case.
2026-06-20 23:35:22 +08:00
Kayshen-X d41cf9c61f fix(panels): anchor caret blink on per-frame search inputs
The component/icon/font/image search boxes rebuild their TextInputState
from the filter String each frame; with_text left the blink anchor at 0,
so the caret could be in its hidden phase right after a keystroke. touch()
the rebuilt state with now_ms so the caret stays steadily visible while
the box is focused (codex review follow-up).
2026-06-20 23:26:21 +08:00
Kayshen-X 81f6d126ab fix(panels): make the icon picker list scroll
The picker loads up to 120 local + remote icons but only ever painted a
fixed ~9-row window + a load-more button, so the rest were unreachable
(no scroll) and the window left a blank gap at the bottom. Render the
list as a real scrolling viewport instead: clip to the list area, shift
all loaded rows by the wheel offset, cull off-screen rows, load-more last.

Wire scrolling through every active path — a shared try_scroll_icon_picker
called from both hosts' apply_wheel AND apply_pan_gesture (trackpad) — and
reset the offset to the top on every search edit (type / backspace /
select-all-clear) so a new filter starts at row 0.
2026-06-20 23:22:33 +08:00
Kayshen-X 5af58072e8 fix(panels): hug-content hover washes + grayed top-bar compound icons
- property / variables panels: shrink the size-checkbox, alignment,
  variant-header and add-variable hover washes to hug their content
  (+ small L/R padding), clamped to the hit rect so long locales can't
  bleed into a neighbouring control; left-align the add-variable footer
  pill so it no longer floats in from the panel's left.
- top bar: paint the folder + globe compound buttons via the shared ghost
  feedback (muted at rest, foreground on hover) instead of SelectTrigger's
  always-foreground icon, matching the sibling icon buttons.

Hit targets are unchanged; only the painted highlight / colour shifts.
Wash regression tests split into property_panel_wash_tests.rs to keep the
test file under the 800-line cap.
2026-06-20 23:21:42 +08:00
Kayshen-X 3617954122 refactor(panels): render image search/generate popovers via unified TextInputView
Migrate the image-node Search query + Generate prompt off hand-rolled
draw_text + manual caret onto the shared paint_text_input_view helper. A
jian TextInputState is rebuilt from each String per frame; the open
popover reads as focused. The generate prompt renders single-line at the
top of its textarea box (multi-line wrapping stays a follow-up). Caret is
now family-aware (no drift) and shares one implementation.
2026-06-20 22:32: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 a728a0b8ae refactor(panels): render component/icon/font search via unified TextInputView
Migrate the component-browser, icon-picker, and font-family-picker search
fields off hand-rolled draw_text + manual caret onto the shared
paint_text_input_view helper. A jian TextInputState is rebuilt from each
filter String per frame (with select-all carried through where present),
so the caret is family-aware (no drift) and shares one implementation;
the panels thread now_ms for the caret blink (for_editor_at).
2026-06-20 22:07:38 +08:00