Conflict: crates/doc/src/build.rs: next's parent_page_for (it calls Spread::parent_page_for_side); the PR's test kept
Conflict: crates/tools/src/snap.rs: shown_parent_page_index no longer exists (snap calls Spread::parent_page_for_side); the PR's test kept, calling parent_page_for_side on a two-page and a one-page parent
Conflict: crates/doc/src/page.rs (auto-merged, edited): parent_page_for_side picks the parent's first page for PageSide::Single, the PR's rule moved to where next makes the choice
The tool-cursors merge (ba6fcbe2) appended the branch's pen cursor tests
after next's live_corner_diamonds_follow_a_rotated_frame without that
test's closing brace.
Conflict: crates/tools/src/tests.rs: both sides' tests (thread ports, pen add/delete)
Conflict: crates/ui-egui/src/canvas.rs: the branch's cursors::show for the icon, then next's loaded-text/thread badge; next's cursor_icon dropped for cursors::system
Conflict: crates/ui-egui/src/lib.rs: DesignApp fields from both sides
Tool cursors are glyphs drawn in code (black ink, white halo): the filled
arrow for Selection, the hollow arrow for Direct Selection, the pen nib
with an add, delete, close or convert badge, and a curved double arrow
for rotating. Linux and Windows show them as OS cursor bitmaps,
rasterized per pixels-per-point and cached; macOS paints them at the
pointer, since it sizes cursor bitmaps in points; the web build keeps
the system cursors. Text, hand, zoom and resize cursors stay system
cursors.
The Pen tool reports what a click does: over the first anchor of the
path being drawn it closes the path, and between paths a click on a
selected path's anchor deletes it and a click on its outline adds one.
The Add, Delete and Convert Direction Point tools show the matching pen
badges.
The glyph geometry and the rasterizer are adapted from VectorCraft's
code-drawn tool cursors (same organisation, original work).
Fixes#438
A press with the loaded text cursor places or threads text, so the selected frame's live-corner widget and diamonds are not drawn meanwhile (pointer and cursor already give the loaded cursor priority).
Conflicts:
- crates/tools/src/lib.rs: Overlay keeps next's ContentGhost and adds
LiveCornerWidget and LiveCornerDiamond.
- crates/tools/src/select.rs: the module doc keeps next's threading and
content paragraphs and adds the live-corner one. Drag keeps Place and
adds Corner; SelectionTool holds both `loaded` and `live`. pointer()
first ends live-corner editing when the selection changed, then runs
next's loaded-text-cursor and double-click handling. An empty-canvas
press clears `live` and keeps next's `content: false` Pending. drag()
handles Place and Corner. overlays() keeps next's Place marquee and
content ghost and appends the widget or diamonds.
- crates/ui-egui/src/canvas.rs: the overlay pass keeps next's doc/layout
for ContentGhost and next's Path drawing, and adds the two live-corner
arms (the branch's no-op Path arm is dropped). The live-corner render
test goes before next's fast_harness tests.
- crates/engine/src/cmd/object.rs: next's text_wrap stays; the branch's
corner_options function follows it.
- crates/engine/src/tests.rs, crates/tools/src/tests.rs: both sides'
tests kept.
- ROADMAP.md: the live corners progress-log line goes first; the status
line names it too.
Action::Handled is matched in engine tooling.rs; no other crate matches
Action or Overlay exhaustively.
A selected, unlocked rectangle frame at least 40 px on screen shows the
yellow widget near its top-right corner. Clicking it shows a diamond at
each corner. Dragging a diamond along its edge sets the size of all four
corners as one undo step (Shift: that corner only), clamped to half the
shorter side. Alt-click steps that corner's shape; Shift+Alt-click steps
every corner. Escape, a click on empty canvas, another selection or
another tool ends corner editing. Positions follow the frame's transform.
object.cornerOptions takes corners: four {shape, size} entries. Tools
gain Action::Handled and the LiveCornerWidget/LiveCornerDiamond overlays,
drawn by the canvas in the live_corner theme token. Fixes#186.
Conflict: crates/ui-egui/src/canvas.rs draw_tool_overlays: the PR's
Overlay::Path arm (draw_path_overlay) and next's Overlay::ContentGhost
arm both kept; draw_path_overlay sits before draw_content_ghost.
Direct Selection selects and drags a frame's content, the Selection tool
drags it by the content grabber, and double-clicking a frame with content
toggles between frame and content, alongside #84's text threading and
canvas click counting.
Conflicts:
- crates/tools/src/select.rs: module doc keeps both paragraphs (ports,
content). The frame/content toggle runs on the second press of a double
click (Down with clicks 2, or a DoubleClick from the control channel),
Selection tool only, after threading ports and the text-frame
double-click, so those keep priority; the DoubleClick arm stays empty.
Overlays keep the Drag::Place marquee and add the content ghost.
- crates/tools/src/tests.rs: both sets of tests and fixtures kept; the
graphic fixture sets Graphic::wrap; the double-click toggle test drives
the click-count sequence and the DoubleClick form.
- crates/engine/src/tests.rs: both sets of tests kept.
- crates/engine/src/cmd/object.rs: both test modules kept.
Direct Selection selects and drags a frame's content; the Selection tool
drags it by the content grabber (hand cursor, shown on hover too) and
double-clicking a frame toggles between frame and content. Arrow keys
nudge a content selection. transform.move takes content?: bool (default:
the selection's content flag) and moves the placed graphic or pasted-in
items in the frame's inner space; nested items move in their container's
space. A content drag shows the whole graphic faintly with its bounds.
The Pencil tool's stroke only appeared once the mouse button was
released. The tool reported the stroke so far as a path overlay, but the
canvas skipped path overlays (they were never drawn), so nothing showed
while drawing. The canvas now draws them: curves flattened to about a
quarter of a screen pixel, dashed when asked. That also brings back the
Smooth and Erase tools' dashed drag previews.
The Pencil's preview was black, lost on a dark pasteboard; it is drawn in
the colour of the layer the path goes on, as InDesign does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A right-to-left bound spread lists its right page first, parent spreads
included. The parent page shown behind a document page was picked by list
index (first = left), so every page of an imported right-to-left document
showed the other side's parent items. Pick it by position instead: the
leftmost parent page for a left page, the rightmost for a right page.
New parent spreads in a right-to-left document list their pages right
first, and changing the binding reorders existing parent spreads, so IDML
export writes them in binding order. Spread bleed on the canvas and in
spread PDF export takes the outer edges by position as well.
Conflict: crates/ui-egui/src/lib.rs: next's missing-fonts test; the branch's side repeated control_bar_shows_its_second_row (next's newer copy stays) and key_names_sent_as_text_are_dropped, whose IME version replaces next's copy in place
The Type tool shows an input method's marked text in the story as a
preview of text.insert, underlined (the converting clause twice), with
the candidate window at the caret. The commit is one text.insert and one
undo step; a cancelled composition leaves the document as it was. Keys,
shortcuts, the clipboard and Undo wait while the IME composes; a click,
a tool or document switch, or any journaled command keeps the marked
text as typed. On macOS the app tells the input context to drop marked
text the tool kept.
With a split window, only the pane last clicked takes keyboard events
(both panes saw them, so typing went in twice).
The web build drops only real key names from text events, and nothing
while its text input has the keyboard, so autocompleted words survive.
The control channel's ui.ime, the headless MCP backend's ui.ime and the
MCP ime tool send IME events.
Conflict: crates/tools/src/snap.rs: first_on_axis: a guide or margin in reach competes with the baseline grid through #84's nearer(); otherwise #309's smart-guide passes run and the baseline grid comes last. #84's nearer_of_margin_and_baseline_wins and #309's tests pass.
On the vertical axis the baseline grid was tried before smart guides.
It snaps while hidden, and with lines every 12 pt and a box's top,
centre and bottom each reaching 4 pt either side, one of them is nearly
always in the zone. It took almost every vertical move and drew nothing,
so object alignment and equal spacing hardly ever showed on y. The
baseline pass now runs after the smart guide passes.
Smart spacing also only matched a gap between two other boxes. A box
dragged between two neighbours now snaps to where both of its gaps are
equal, and both gaps are marked.
Fixes#188
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5fQBTFb4hhKXsCFYitZ1P
Resizing or drawing at a zoom where the snap zone covers the baseline
grid's spacing (66% and below with a 12 pt grid) always snapped to a
baseline line, so frames could not reach the top or bottom margin. The
baseline grid and the guides (ruler guides, margins, columns) now
compete by distance and a tie goes to the guide.
Frames drawn with the Type tool snapped nothing; they now snap like the
frame tools' (press point and moving edges, with Shift and Alt).
- Double-clicking a text frame with the Selection tool edits its text. The
canvas counts presses itself (egui reports double clicks only on release,
so the press never saw them): 2 clicks select the word or run of spaces
under the pointer, 3 the line, 4 the paragraph, 5 the story
(`text.selectAt`; pointer events carry `clicks`).
- The caret moves past spaces typed at the end of a line: trailing spaces
follow the line undrawn instead of being dropped.
- Bold and Italic (Shift+Cmd+B / Shift+Cmd+I, B and I in the Character
panel) toggle the family's own styles per run, keeping the other trait
(Italic + Bold = Bold Italic). Formatting chosen at a caret applies to
what is typed next.
- Copy, Cut and Paste reach the system clipboard however they are chosen,
including from the native macOS menu; without a native menu the copy and
paste keys work for objects too.
- Formatting Affects Text (J, Tools panel) sends swatches, colours, tints,
None, Swap and Default to the text of the selected frames; the proxy and
the Swatches and Color panels show the text's colours.
- view.snapPreferences {alignEdges, alignCenters, smartDimensions,
smartSpacing, zone} sets the smart guide options; the Preferences
dialog goes through it, so the control channel and MCP reach them.
- Smart guide, gap and measurement colours come from theme::Tokens.
- Remove the unused closest-target helper (snap_rect and friends).
- Drop the agent planning docs (docs/superpowers) and the .worktrees
ignore line; ROADMAP entry for smart guides and snapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift plus Alt restores the aspect from the original center after the size snaps. A line keeps the runner delta on both axes. Dimension guides are redrawn on the committed side. Grouped own size decomposes the absolute transform once.
Commands return errors instead of expect()/unreachable!() (book, library,
undo/redo, paste, find/replace, Pathfinder, tables, QR, select, swatches,
anchors, articles, transform again, XML import). The sample document builder
propagates errors. Commands that add a key to their parameters no longer
panic when a caller (control channel, MCP, script) passes an array or a
string, and corrupt crash-recovery metadata is ignored. engine, mcp and
tools now deny unwrap/expect/panic/unimplemented/todo/unreachable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>