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.
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).
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.
- 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.
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.
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).
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).
Add a general paint_text_input_view helper (placeholder + focused params;
paint_text_input_view_value now delegates to it) and route the variables
search/filter box through it instead of a hand-rolled draw_text + manual
fill_rect caret. The search_input TextInputState was already rebuilt each
frame, so this is render-only — the caret now uses the family-aware
measure (no drift) and shares one implementation.
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.
Click an R/G/B box to edit that channel as a 0..255 value through the
unified TextInputView; the host routes keystrokes via color_picker_rgb_*
(now_ms-threaded, digit-gated, 3-char cap) and live-applies by splicing
the channel into the current RGB and committing through set_hsv.
ColorPickerState gains rgb_focus + rgb_input; active_text_input + the
text-input gate resolve it; press dispatch focuses/blurs hex vs rgb.
Extracts the hex+rgb edit state machines into color_picker_edit.rs to
keep color_picker.rs under the 800-line cap. Bumps vendor/jian to d11d54e.
The picker hex field now stores a jian TextInputState (hex_input) seeded
on focus and edited via the same caret-aware insert/backspace gating as
the property-panel hex input, instead of a hand-rolled String draft +
manual caret. ColorPickerState carries hex_input; the host threads now_ms
for the caret blink and routes keystrokes through color_picker_hex_char/
backspace(now_ms); active_text_input() resolves the picker hex so its
blink drives the redraw. Fixes the caret drift and removes the picker's
last hand-rolled input.
Bumps vendor/jian to cce4523.
Implement Painter::measure_text_family on the native skia backend
(NativeBackend::measure_text_family_styled segments per-codepoint
against the real family, exactly like draw_text) and forward it through
NativeFrameBackend + BaselineAdjustingBackend so the family-aware
measure reaches skia through the wrappers. Fixes the ~6px caret drift
in every TextInputView/TextArea chrome input (property pills, chat, git,
settings) — measure now uses the same typeface paint does.
Bumps vendor/jian to 19fb00b (Painter::measure_text_family).
Editable hex input pushed color_picker.rs to 979 lines. Extract the two
self-contained helper clusters into siblings:
- color_convert.rs — pure HSV/RGB/hex math (re-exported through
color_picker so crate::color_picker::parse_hex_rgb callers are unchanged)
- color_picker_snapshot.rs — variable/fill/effect hex extraction for the
history-change check (pub(crate))
color_picker.rs is now 769 lines; no behaviour change, 17 picker tests green.
Click the picker's hex bar to focus it, then type a #RRGGBB value
(or paste one char at a time): the bar shows the live draft + caret,
Enter/Escape/outside-click commit-and-blur, and a valid hex re-seeds
HSV so the SV box + hue handle follow. Keystrokes route through the
text-input gate so they don't leak into chat/shortcuts.
Bumps vendor/jian to ce19bed (ColorPicker::HexInput hit + draft render).
A consistency audit found per-item/per-token hover regressions from the
jian migration: provider cards gave ClaudeCode a permanent muted fill
while others were transparent (now all transparent + one button_hover
wash); layer-panel page/layer rows, the flex padding-mode popover rows,
and the builtin-preset rows washed with theme.muted instead of the
jian-standard theme.button_hover. Hover now reads identically across the
chrome.
The preset dropdown tracked no live row hover (a documented gap). Add a
variables_preset_menu_hover state, update it on cursor-move via the
existing ThemePresetMenu hit-test (swallowing moves over the menu like a
top-most overlay), and paint a jian button_hover wash on the hovered row
(inset a few px so it reads as a padded highlight, not edge-to-edge).
apply_cursor_move only handled the picker while dragging; an open-but-idle
picker fell through to the canvas hover layer, so hovering the picker
bled selection/hover feedback onto the nodes behind it. Swallow moves
whose point is inside the picker panel (clear lower-overlay hover + return),
matching the design-md / modal overlay behaviour.
Move the card-form geometry (type_toggle_rect / field_input_rect /
form_actions_y / form_card_h + their constants) into the
agent_settings_acp_helpers sibling so agent_settings_acp.rs drops back
under the 800-line cap (829 -> 784). A saved card being edited shows no
Save/Cancel row, so card_height wraps just the fields (form_actions_y),
not the draft's action-row tail — restoring the Local saved-edit height
(332) the previous commit inflated to 370.
The ACP draft/editing card height + Save/Cancel row position now derive
from the connection type's field set, so a Remote agent (Display name +
URL) no longer leaves a tall empty gap below the URL field that Local's
(Command/Args/Env) layout reserved. The disabled Save button renders as a
neutral Outline (white in light mode) instead of a washed-out pale-primary
fill.
The TopBar globe + file-menu SelectTriggers now pass bordered:false (no
Outline box, just the icon+chevron with a hover wash); the form-field
dropdowns keep the border. The git commit-message TextArea (ready +
status panels) now paints through BaselineAdjustingBackend so its text
sits on the baseline instead of clipping at the top. Bumps vendor/jian.
jian a53a61c: ColorPicker's box-centered labels now route through
centered_text_baseline_y like every other component; non-box-centered
placements (RGB captions, Dialog title) and the host-baselined input
glyph layers are documented exceptions.
jian dc15ea7: component labels (buttons, tabs, toggles, badges, menus,
dropdowns, tooltips, accordions) now position text by the BASELINE
(center + font_size*0.35) instead of the (height - font_size)/2 top-left
form, so they no longer render ~font_size too high in the editor chrome.
Input glyph layers stay top-relative (the host's BaselineAdjustingBackend
handles their baseline).
The Design-MD panel's markdown parser (block structure + inline
bold/code/#HEX styling + run wrapping) moves to jian components::markdown;
design_md_markdown.rs becomes a thin re-export so the panel's block
renderer is unchanged. ~300 fewer lines of OP-domain parsing. Bumps
vendor/jian to 94f8315.
The floating color picker's rendering, layout, hit-test and HSV<->RGB
math now live in jian ColorPicker; color_picker.rs shrinks to a wrapper
that owns the property-panel-relative anchor (rect), the EditorState
plumbing and the drag dispatch, re-exporting hsv_to_rgb / rgb_to_hsv /
ColorPickerHit / PICKER_* so the hosts import them unchanged. ~330 fewer
lines of OP-specific paint. Bumps vendor/jian to 9580247.
Review found the axis-tab paint (jian content_rects, advance = width+gap)
drifted 6px from the hit walker (theme_tab_advance_width) for tabs to the
right of an inline-renaming axis, where the two advances differ. Feed
content_rects the host's per-tab advances (theme_tab_advance_width for
variables; width+CHIP_GAP for the framework chip) so paint and hit share
the same advance source and can't drift in any state. Bumps vendor/jian
to f0fbf70.
The theme-axis tab strip renders through jian Tabs content-width mode
(Text active style + trailing chevron on the active axis, which doubles
as the axis-value dropdown trigger) instead of a hand-rolled label loop.
The host keeps the inline-rename overlay (an opaque input over the
renaming tab), the +Add / Preset / Close buttons, and the theme_tab_rect
hit walker — whose geometry matches content_rects for normal tabs.
The scrollable, variable-width framework chip carousel now lays out via
jian Tabs::content_rects and renders via Tabs::paint_content (PrimaryPill
active style) instead of a hand-rolled chip loop; the host keeps its
deterministic width fn, scroll state, clip band and chevrons. Both paint
and hit go through the jian layout, so they stay aligned. Bumps
vendor/jian to ed15b1e (Tabs content-width flexibility).
paint_section_divider (11 call sites), paint_hairline (4), and the two
paint_divider helpers (7) now draw through jian Separator — the canonical
1px-line primitive — instead of ad-hoc fill_rect; the top-bar one is a
vertical separator. First OP consumer of jian Separator; ~22 dividers
share one primitive with no caller changes.
The IconButton migration pushed ai_chat_panel.rs to 801 lines (over the
repo's 800-line cap). Move draw_label / chat_neutral_hover_color /
chat_neutral_feedback_color into the ai_chat_panel_controls sibling
(backend trait already in scope there) and re-export them, so callers
and tests are unchanged. ai_chat_panel.rs is now 780 lines.
The AI-chat header (New Chat / maximize), component-browser header
(close + export/import kit), icon-picker close, and figma-import close
buttons now render through jian IconButton instead of hand-rolled
paint_ghost_button_feedback + draw_icon. Drops the now-orphaned
paint_header_btn_bg helper; host hit-tests are unchanged.
The gradient-stop and shadow-color swatches now paint through jian
Swatch (alpha-aware checker + fill) instead of the local paint_alpha_checker
+ separate fill; drops paint_alpha_checker and its public re-export.
Bumps vendor/jian to 9c13e8d (checker now theme-independent, so the
transparency pattern stays visible in dark mode).
Codex/multi-agent review found the text h/v-align ToggleGroup hit rects
sat 14px (LH_LS_LABEL_H) above where they paint: text_action_rects
omitted the 行高/字间距 caption row that paint_text_section and
push_text_input_rects both include, so the bottom half of each align
toggle was a dead zone and the band above misfired. Add the missing
LH_LS_LABEL_H term. Also refresh two tests left stale by the jian Button
migration (kit-dropdown pressed-feedback radius 4→6; code Export-bundle
hover wash now jian Ghost's button_hover, not a boosted alpha).
The widget + size-section check rows and the flex gap-alignment radio
now paint through jian Checkbox / Radio (first consumers) instead of
hand-rolled box+check / ring+dot; row hit-tests are unchanged.
property_panel_sections::paint_flex_section was an unreferenced leftover
from when the flex section moved to property_panel_flex (the only wired
paint_flex_section). Removes the stale gapped-cell duplicate.
The Free/Vertical/Horizontal layout-direction icon selector renders
through jian ToggleGroup (icon segments, font_size 14 → 18px icons) over
the same footprint; its hit walker switches to the continuous even cells
ToggleGroup paints so paint + hit stay aligned.
The horizontal/vertical text-align icon selectors render through jian
ToggleGroup (icon segments, font_size 12 → 16px icons); their hit cells
switch from gapped to the continuous even cells ToggleGroup paints so
paint + hit stay aligned.
The Auto/Fixed-width/Fixed-width+height text-growth selector renders
through jian ToggleGroup; its hit walker switches from gapped cells to
the continuous even cells ToggleGroup paints, so paint + hit stay aligned
with no inter-cell dead zones.
The Contain/Cover/Stretch mode selector renders through jian ToggleGroup
(even cells aligned with its existing full-cell hit walker) instead of a
hand-rolled muted track + inset chip loop; active cell fills primary.
The Anthropic/OpenAI kind segmented control now renders + hit-tests
through jian ToggleGroup (segment_at) instead of a hand-rolled cell
loop + kind_option_rect. Drops the KindOption.slot indirection and the
kind_option_rect helper; single-option presets render full-width.
Bumps vendor/jian to 3b5db91.
All op-editor-ui jian Button call sites move from icon_d to icon_paths.
The code panel's Generate / Export-bundle icon+label buttons and the
Design-MD action buttons now render through jian Button with full
multi-subpath icons (sparkles / braces / wand), dropping the local
paint_centered_icon_label hand-rolling + paint_primary_hover_overlay.
Bumps vendor/jian to 9998df9.
Export-section, export-dialog (Cancel→Outline, Export→Primary), image
generate-popover (Apply→Primary, Retry→Outline) and the code-panel
full-width action button now render through jian Button instead of
hand-rolled fill_round_rect + hover overlay + centered draw_text. The
solid-fill hover-darken now lives in the component, so per-button hover
state threads straight through. Bumps vendor/jian to 330feb4.
Connect renders through jian Button Primary (centered label, primary
fill) and Disconnect through the new DestructiveOutline variant (red
border + red label) instead of hand-rolled fill_round_rect + centered
draw_text. Bumps vendor/jian to 9e47bd3.
Fill section head-row fill-type dropdown now renders through jian
SelectTrigger (Outline box + chevron + own hover/press feedback)
instead of a hand-rolled label+glyph; the Design-MD empty-state action
buttons render through jian Button (Secondary, muted fill) so the CTA
affordance and feedback are the component's concern, not the panel's.
Drops the now-dead label_char_w helper.
The kit-filter dropdown trigger (feedback wash + border + truncated label + chevron) now renders via jian SelectTrigger — one component call, feedback/label/chevron all internal. Demonstrates the dropdown 收口 pattern. Bumps vendor/jian to 144eb50.
top_bar paint_icon_button, design_md icon_button, and the align-toolbar align-icon row now render via jian IconButton instead of hand-rolling feedback + draw_icon. Their hover/active rendering is now the component's concern. align-toolbar idle icons go muted_foreground (shadcn toolbar look). Left compound (icon+chevron/brand-logo), labelled, and text-glyph buttons untouched — they aren't single lucide-icon buttons.
toolbar's paint_button now renders jian IconButton (icon.paths() -> generic d-strings); removed the OP-local IconButton struct + paint_icon_button helpers from button.rs (the component now lives in jian). Bumps vendor/jian to 346eeb3. Other scattered icon buttons migrate next.
Adds button::paint_icon_button (full icon button = hover/pressed/active feedback on jian Button + centred icon) and paint_icon_button_feedback (the bg+active funnel). toolbar's paint_button now delegates to it instead of hand-rolling active-fill + ghost-feedback + draw_icon separately. All toolbar icon buttons (tool buttons + shape slot) now funnel hover/active rendering through one place. Active highlight now jian Button Primary (r6). Removed dead BUTTON_RADIUS.