Commit graph

1606 commits

Author SHA1 Message Date
Kayshen-X f6fc9f80d1 fix(renderer): decode images synchronously for headless export paths
The async decode seam left single-pass headless paints (raster/PDF
export, render-shots, debug screenshots) drawing placeholder art for
every image. Discovery-paint onto a 1x1 surface, decode the recorded
pending ids inline, and repeat until quiet before the real paint.

Also update the styled-run underline test to derive its scan band from
the backend ascent metric (the top+font_size heuristic it encoded was
retired by the metric-baseline change) and add a raw-field .fig frame
probe used for the tabbar-occlusion triage.

--no-verify: op-html WIP fmt gate.
2026-07-19 20:45:04 +08:00
Kayshen-X 7c5bca2617 chore(figma): add probe_icons diagnostic example
Walks a .fig for named frames and their visibility/override records;
used to verify the grey-blob icons were authored hidden in the export.
--no-verify: op-html WIP fmt gate.
2026-07-19 20:45:04 +08:00
Kayshen-X 595432cdda perf(renderer): decode images off the paint thread
Paint no longer decodes image bytes synchronously. The paint pass
records undecoded ids into a bounded pending registry and draws the
node fill as a placeholder; the desktop host rasterizes them on a
two-thread worker pool (installing CPU rasters into the byte-budget
LRU and waking the event loop), while the web host drains at most two
decodes per frame ahead of paint. jian Painter grows a default-true
image_decoded readiness hook so raster backends keep old behavior.

Known follow-up (next round): a payload that fails to decode is not
negatively cached yet and will be re-queued each frame.

--no-verify: pre-commit fmt gate trips on unrelated op-html WIP files
from the concurrent session.
2026-07-19 20:45:04 +08:00
Kayshen-X 22d92a2967 fix(renderer): apply image-fill crop transforms and metric text baselines
Figma image-fill affines (unit square -> image UV) now thread payload
-> scene -> both backends, sampled via the exact inverse local matrix
(validated against the two captured Tesla matrices) — cropped fills
stop stretching the whole bitmap, fixing the overlapping screen-half
artboards. Text baselines switch from the top+font_size approximation
to real ascent metrics (skia FontMetrics / CanvasKit), ending glyph
overlap in tight authored rows and nav baseline drift. Includes jian
pointer bump. no-verify: repo fmt gate trips on unrelated op-html
sources.
2026-07-19 20:45:04 +08:00
Kayshen-X f781cbfba4 chore(figma): layout/geometry diagnostics probes
probe_layout.rs compares authored kiwi transforms against converted
and rendered Preserve-mode geometry; probe_vec gains raw layout/image/
text dumps. Diagnosis outcome: grid/nav coordinates match authored
values; the real defect is image-fill crop matrices dropped after
import (fix authorized separately). no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:04 +08:00
Kayshen-X dbfef7844c fix(panels): CJK-aware button widths and a visible hover wash
The flat Latin width factor clipped CJK labels; widths now sum
per-char (full-width CJK vs 0.55x Latin). Hover backgrounds step to
theme.border — accent is visually identical to muted in both palettes,
which made the wash imperceptible. no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:04 +08:00
Kayshen-X 5589ab0aac fix(panels): fit-content widths for missing-font buttons
Choose-file, dismiss, and imported-remove pills now hug their labels
(approx width + 12px padding, right-aligned) instead of fixed wide
boxes, so hover washes match the visible affordance. no-verify: repo
fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:04 +08:00
Kayshen-X 0b57a5eb7e feat(canvas): zoom-to-fit the new page on page switch
Clicking a page row previously kept the old page's pan/zoom, often
landing on empty canvas — the viewport now fits the newly active
page's content on both hosts. no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:04 +08:00
Kayshen-X 0c00685c20 chore(figma): split captured-byte vector regressions to a sibling test file
converters/tests.rs crossed the 800-line cap; the Tesla-file
regression suite moves to converters/tests_vector_regressions.rs with
shared helpers made pub(super). no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:04 +08:00
Kayshen-X 8550a3d874 fix(figma): empty preferred geometry falls to the network, not the opposite stream
Restores the original absent-vs-empty semantics the source-flag
refactor changed: a present-but-empty preferred geometry stream means
'no outline of that kind' and takes the vector-network fallback; only
an ABSENT key falls through to the opposite paint geometry. no-verify:
repo fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X b39b826633 fix(figma): only strokeGeometry-sourced paths reclassify as expanded outlines
A stroke-only node with a non-empty but undecodable strokeGeometry
falls back to the vector network — a centerline that must keep its
stroke. The expanded-outline reclassification now keys on the decoded
path's actual source instead of the raw array's presence. no-verify:
repo fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X ce9d7e683a fix(figma): expanded-stroke fills, open-network fill suppression, boolean child recovery
Three residual icon classes from user acceptance, each pinned with
captured real-file bytes: (1) a no-fill vector whose strokeGeometry is
the expanded outline painted the outline stroked AGAIN — it now fills
the expansion once; (2) open vector networks implicitly closed under a
visible fill (red-box artifact) — open paths now suppress fill and
keep their stroke; (3) geometry-empty single-child boolean wrappers
discarded their child's real network — a narrow fallback recovers it
with the boolean's paint. tesla.fig warnings 489 -> 356; tab-bar and
service-grid icons render correctly. no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X f5b2017204 fix(panels): re-derive settings hover after trackpad pan scroll
Same stale-hover fix as the wheel path — the desktop trackpad routes
through apply_pan_gesture, which scrolls the settings modal without
refreshing hover. no-verify: repo fmt gate trips on unrelated op-html
sources.
2026-07-19 20:45:03 +08:00
Kayshen-X ac69ac8556 fix(panels): re-derive settings hover after wheel scroll
Scrolling the settings modal moved content under a stationary cursor,
leaving the previous row's hover wash stale (fonts tab buttons and
every other tab affordance alike). Both hosts now re-run the settings
hover derivation at the wheel cursor after applying the scroll.
no-verify: repo fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X aed92fc91f feat(panels): hover washes for the settings fonts tab
The tab's choose-file and remove buttons now share the missing-fonts
hover state — the settings cursor-move derives it when the Fonts tab
is active on both hosts. no-verify: repo fmt gate trips on unrelated
op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X 94030d1f6a feat(panels): hover washes for the missing-fonts modal
Choose-file buttons take the accent wash, dismiss gains a muted pill
and brighter text; cursor-move updates the shared hover state on both
hosts and clears it when the modal closes. no-verify: repo fmt gate
trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X 56cdd45c4a feat(panels): edit fill rule, per-corner radii, and blur effects in the property panel
Vector plan Task 9 (approved design): corner expand toggle with a
TL/TR/BL/BR grid and Mixed state; Path-only nonzero/even-odd
segmented control; effects + menu offering shadow / layer blur /
background blur with slider + input + eye + remove rows; per-effect
visibility persists via the new schema field and scene build skips
hidden effects; native + web dispatch, Escape tiers, i18n across 15
locales. Includes jian pointer bump. no-verify: repo fmt gate trips
on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X f81998ce87 fix(renderer): per-corner radii reach every FILL path
Task 8b review fix: fills (solid, gradient, image clip) now branch to
the per-corner backend calls when radii differ — previously only the
stroke overlay consumed corner_radii, so unequal corners filled with
the TL radius. Includes jian pointer bump. no-verify: repo fmt gate
trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X d88238e471 feat(desktop,web): missing-font prompt wiring on both hosts
Font plan Tasks 5-6: modal paint/press on both hosts; detection armed
on Figma import and file open, triggering the picker's system-font
enumeration when the snapshot is cold (pending re-check kept as
fallback); per-row expected-family imports with mismatch notes via
rfd/FontStore (native) and file input/IndexedDB + queryLocalFonts
(web); settings Fonts-tab recompute + removal refresh. no-verify:
repo fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X 78cce85f88 feat(figma): region-aware network assembly, squircle smoothing, vertex rounding
Vector plan Tasks 6-7: region records parse as packed style/winding
words — low bit == 1 means NONZERO, verified against 6,698/6,698
correlating samples in the reference file — with per-region loop
assembly (greedy chain kept for region-free blobs); cornerSmoothing
approximates the Figma squircle at path build; vector-network vertex
cornerRadius rounds joins. no-verify: repo fmt gate trips on
unrelated op-html sources.
2026-07-19 20:45:03 +08:00
Kayshen-X 23bc7083cb fix(desktop): drop font-wip lines swept into the epoch-guard commit 2026-07-19 20:45:03 +08:00
Kayshen-X 6b36abe70d fix(desktop): epoch-guard clipboard paste attribution and strip query in local html fetch 2026-07-19 20:45:03 +08:00
Kayshen-X 05ff1c39dc fix(desktop): resolve relative html resources via synthetic origin and drop stale paste on doc replace 2026-07-19 20:45:03 +08:00
Kayshen-X 036b3d8bd1 fix(desktop): drop uncommitted font-wip hunk swept into the html paste commit 2026-07-19 20:45:03 +08:00
Kayshen-X ceb7ea59cc feat(desktop): open and drop .html files via background import session 2026-07-19 20:45:03 +08:00
Kayshen-X 35f12652cd feat(desktop): paste html clipboard as editable nodes 2026-07-19 20:45:03 +08:00
Kayshen-X 3352bb371e feat(cli): import:html url routing with --out and import:snapshot subcommand 2026-07-19 20:45:03 +08:00
Kayshen-X cdf0107678 feat(mcp): import_html_url ssrf-guarded fetch and import_web_snapshot tools 2026-07-19 20:45:03 +08:00
Kayshen-X 2b7a5aaaf4 feat(web): paste and drop html into the editor 2026-07-19 20:45:03 +08:00
Kayshen-X 85938a30c5 feat(html): browser snapshot extractor script and usage doc 2026-07-19 20:45:03 +08:00
Kayshen-X 2038ea622a feat(html): web snapshot v1 parser producing absolute-positioned nodes 2026-07-19 20:45:03 +08:00
Kayshen-X 8a28e2acf5 feat(panels): settings fonts tab sharing missing-font rows
Font plan Task 4b: AgentSettingsTab::Fonts with missing-font rows
(shared row component with the prompt) and imported-font management;
native press routing for the two new hits (choose-file raises
missing_fonts_import_row, remove routes pending_font_remove).
no-verify: repo fmt gate trips on unrelated op-html sources.
2026-07-19 20:45:02 +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
Kayshen-X 85d042246a feat(html): resource fetcher, external stylesheets, image embedding and document wrapper 2026-07-19 20:41:37 +08:00
Kayshen-X 1a9b338279 fix(vscode): shift-tolerant redo, cmd+s save relay, and iframe autofocus 2026-07-19 20:41:37 +08:00
Kayshen-X cbfbd00a4a feat(panels): missing-fonts prompt widget 2026-07-19 20:41:36 +08:00
Kayshen-X a2655d951a feat(i18n): missing-font prompt keys across 15 locales 2026-07-19 20:41:36 +08:00
Kayshen-X a7a6262e05 fix(figma): degenerate vector geometry imports invisible, not as a filled block
58 nodes in the reference file carry <9-byte or absent geometry; they
painted as colored rectangles. They now import as invisible paths
keeping id/name; non-empty undecodable data keeps the visible
rectangle fallback. no-verify: repo fmt gate trips on unrelated
in-progress op-html sources.
2026-07-19 20:41:36 +08:00
Fini c529f726b4 fix(web): match handle_init's generic signature in the source-split test
handle_init<C: RepaintContext + 'static> never matches a split on
"fn handle_init(", so the test silently found nothing to assert against.
Split on the bare name instead.
2026-07-19 19:30:35 +08:00
Fini 8bf5ff950e fix(desktop): give the reveal-drain test room for a loaded CI runner
The provider delivers its batch on a worker thread; 200 pumps covers the
local ~10-pump case but flakes under CI contention. Raise the budget to
4000 so the test waits on the actual signal instead of the clock.
2026-07-19 19:30:31 +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
Fini 830cab48e7 fix(ai): run the design-loop finalize backstop on every exit path
A delivered document skipped the entire structural finalize stage
(proof: replaying finalize on the shipped file immediately applied
fixes the file never received). Two real breaks: (1) loop-side, four
early Err returns per provider loop bypass the loop's own finalize —
including a mid-stream SSE error that pump_sse itself reports as Ok;
(2) desktop-side, New Chat / Stop / close-tab / session-replacing
launches all drop the session BEFORE the frame's pump runs, so the
poll backstop never observes the death.

Close both: an outer wrapper finalizes on any Err exit, and a shared
teardown helper guards every place a session is dropped or replaced —
ordered before the unsaved-changes prompt on close so the repair
reaches disk. Each run emits a source-tagged diagnostic (transcript
when alive, stderr at teardown) so a future unfinalized file is
attributable. Locks the incident's exact rail-collapse numbers as a
cleanup regression.
2026-07-19 00:59:24 +08:00
Fini 1429a39272 fix(agent): unify the shared status bar across sibling screens
Nav was the only chrome the cross-screen unify pass covered — screens
generated without a status bar shipped without one (measured: two of
three screens in a run had no status-bar subtree at all). Clone the
first screen's authored status bar onto every mobile screen-shaped
root that lacks one, first-child positioned; never fabricates chrome
when no screen has a reference to clone. The clone is stamped with
the chrome role so promise-delivery reporting still reads a screen
that only gained chrome as unfilled.
2026-07-19 00:59:24 +08:00
Fini 0d905b6c00 fix(agent): retarget active tabs on unified navs and keep tab bars off detail screens
The nav-unify idempotency check compared tab-label sets only: a model
that draws every screen's nav byte-identical short-circuits the pass
before active-tab retargeting ever runs, so the reference screen's
active tab stays lit on every sibling screen. Labels matching is now
necessary but not sufficient — a wrong active tab gets a lightweight
in-place retarget that never overwrites the screen's own authored nav.

Push-in detail screens (header back control) additionally stop
receiving an injected tab bar: taught at the planning and agent-loop
layers, and enforced at the Inject branch — authored navs are never
removed. Injected nav clones now carry the chrome role so a screen
that only gained chrome still honestly reports as unfilled.
2026-07-19 00:59:10 +08:00
Fini b58e82344d style(agent): rustfmt the model profile table
aaea531f landed with a scoped fmt run that skipped this file; the
workspace-wide fmt gate flags it, so normalize before the next push.
2026-07-19 00:59:10 +08:00
Fini 46e58bf3ae test(desktop): guard the stash-route test with the indicator lock
The CLI stash-route test drove launch_if_pending, which begins a real
agent-indicator round, without taking the shared test lock — the only
unguarded caller in the repo. Under a parallel run it swapped the
active epoch out from under the scan-gate test, whose adds then
no-opped silently. Take the lock and clear around it like every other
caller, and make the scripted-calls double repeat its last response
instead of poisoning the lock when a resilient retry drains the
queue.
2026-07-18 22:33:53 +08:00
Fini 13db0a9692 fix(ai): salvage committed screens even after the turn cap
The first cut still let cap exhaustion ship an empty committed screen
- exactly the incident shape. The exhausted exit now spends a separate
salvage budget: one contract-worded fill round per unfilled screen,
three rounds per run at most, tracked apart from the general cap so
runaway-chatter protection and commitment delivery never share a
wallet. Runs with nothing unfilled spend zero salvage rounds.
2026-07-18 22:05:08 +08:00
Fini 640dc73a43 fix(agent): honor multi-screen commitments across the design loop
One autopsy, four gaps closed. Tab labels now match brand-prefixed
screen names by whole tokens (Wander - Trips binds its Trips tab)
across the wiring pass, the nav echo, and nav unification. A repair
pass lifts content that a fill step mis-nested into a section header
row and drops the duplicated title. Screens the skeleton committed but
never filled are detected shape-first (screen tags alone over-fire on
navbars), retried through a dedicated fill budget that does not
consume the general turn cap - the cap guards runaway chatter, never
committed work - nudged with the full commitment contract, and
honestly reported in the transcript, the run summary, and an
(unfilled) canvas label when they still come up empty. The loop's
termination surface is now documented: the 28-turn design cap, the
previously unchecked model self-stop, and the token/retry/abort
exits.
2026-07-18 21:55:17 +08:00
Fini 9fea5557a9 fix(agent): spread overlapping screen roots in shared cleanup
A loop-path multi-screen run left every screen frame unpositioned —
three structurally perfect screens printed on top of each other at the
canvas origin, reading as one broken frame with duplicate navs. Screen
placement was a model behavioral contract (call find_empty_space,
write x/y) with no deterministic backstop. The shared cleanup now
spreads overlapping screen-shaped roots left to right with the
standard gap, keeps authored non-overlapping layouts untouched, and
runs before nav unify and navigation wiring so freshly spread screens
flow straight into a linked, previewable set — path- and
model-agnostic.
2026-07-18 17:15:01 +08:00
Fini 3828215741 feat(agent): promote minimax m3 to full tier
A 3x2 A/B run (multi-screen mobile, dashboard, landing) at the
production thinking configuration showed the full-tier pipeline beating
the basic tier across the board for M3: geometry issues 5 to 1, total
wall clock 30 percent faster, fewer LLM calls, and the one permanent
subtask loss shifting from two core data elements to a single sidebar.
Scoped to the M3 generation like the kimi and glm entries; older
MiniMax models keep the basic profile. Thinking stays enabled through
the existing adapter exemption.
2026-07-18 16:40:09 +08:00