Commit Graph
4383 Commits
Author SHA1 Message Date
alehel 3f32106425 Merge remote-tracking branch 'origin/main' into refactor/page-press-edit-text
# Conflicts:
#	crates/ui-egui/src/canvas.rs
2026-11-05 14:30:43 +01:00
983e2f2ad6 M3: → / ← scroll a page wider than the window before turning it (#4540)
* → / ← scroll a page wider than the window sideways before turning it

On a plan zoomed in, the arrow keys turned the page (#185) and never showed
its other side: the view moved sideways only by the scroll bar, the wheel or
the Hand. Now → / ← scroll a line that way while there is more of the page,
and turn the page at its edge, as ↓ / ↑ do in single-page view. ⌘→ / ⌘←
turn it at once; a page that fits across turns as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: → / ← read the current page's or spread's edges, not the widest page's; ui-parity's change record

In continuous view the content is as wide as the widest page, so on a narrower current page
that shows whole → scrolled toward the wide page's edge instead of turning. The edges are now
the current page's (or spread's) as drawn, or the content's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: a spread's second page off screen still counts for → / ←

The row's edges came from the pages' screen transforms, and a page gets one
only while it meets the window. With a zoomed two-page spread scrolled so its
second page was still wholly to the right of the window, the row ended with
the first page, so the next → turned the spread instead of scrolling to the
second page. The edges are now taken from the layout rectangles of every page
in the row, placed as the drawn pages are.

A TwoUp case in the viewer tests zooms a spread until its page gap sits at the
window's right edge and checks that → scrolls on rather than turning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: → / ← read the row after continuous view has moved the current page

In continuous view the current page is updated while the pages are laid
out, after the row's extent was taken, so the edges could describe the
previous row and the next → or ← turn the page instead of scrolling the
page that had just become current. The row is now read after that update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: a page turned by → / ← at its edge starts at the edge the reader comes from

Turning at a wide page's right edge with →, the next page kept the
horizontal scroll (the same column, as other jumps keep it), so an equally
wide page opened at its right edge and the next → turned again without
showing its left side; ← had the same in reverse. When plain → or ← turns
the page, a page or spread wider than the window now starts at its left
(after →) or right (after ←). ⌘→ / ⌘←, Page Down / Page Up and every other
jump keep the column.

The sideways-arrow test now goes across two wide pages both ways: → scrolls
the next page from its left edge, ← turns back to the previous page shown
from its right edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: the sideways arrow keys through main's PageEnv, PageOut and GlideCx, and shown_row with its layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 14:25:56 +01:00
FredApps c56a63bcb6 Merge pull request #4732 from helenkwok/web/host-page
feat(web): page host mode, so a page can host PdfCraft in its own document (#4731)
2026-11-05 14:20:12 +01:00
96fe832e12 M5: Shift holds lines level or plumb while drawing (ortho) (#4285)
* M5: Shift holds lines level or plumb while drawing (ortho)

With Shift held, a new line or arrow runs level or plumb from where it
starts, whichever is nearer to the pointer (a CAD drawing's ortho mode),
and each new side of connected lines, a polygon or a cloud is held the
same way from the corner before, rubber band included. Dragging an end
or corner of a drawn one with Shift is held the same way; it turned to
45° steps before. The measuring tools keep their 45° steps.

measure::snap::ortho next to constrain; comment_add takes `ortho` for
line, arrow, polygon, cloud and polyline. Parity comment.draw-ortho.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ortho: over the first corner the rubber band shows the closing side, Shift or not

From review: a click there closes the polygon or cloud to its first
corner, while the preview with Shift held showed a level or plumb side.
The click and the preview now share one rule (closes_at).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: Shift is read where main's split put the drawing input, and the ortho helpers are imported there

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: Shift holds a drawn line, not a freehand stroke (a hunk had landed in the wrong branch)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ortho: the parity note names the closing side's exception; the test's doc comment is back above its test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 14:01:18 +01:00
FredApps 419b267ee5 Merge pull request #5125 from huppiflupp/feat/scan-clean
M10: Enhance scanned file cleans scanned pages in place
2026-11-05 13:36:55 +01:00
ohagmannandClaude Opus 5.5 04cee34bfe M5: a double-click on a comment card's text edits it (#4544)
* A double-click on a comment card's text edits it

On the selected card in the Comments panel only the header took the
double-click: the text is a selectable label, so a double-click there
selected a word and the editor stayed closed (a tester's report). It now
opens the editor too; dragging over the text still selects it for copying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: the double-clicked card's editor is drawn too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: the double-click test also starts from a card that isn't selected

It selected the card before any input, so it never covered what the change
is for in the Comments panel as one uses it: a first click on the text of an
unselected card selects it, the second edits it. The test now runs both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:21:29 +01:00
FredApps 65aa495c98 Enhance scanned file: re-render the page shown after a successful run 2026-11-05 13:19:49 +01:00
ohagmannandClaude Opus 5.5 8d569066b8 M5: Escape leaves a Fill & Sign tool, as it leaves a comment tool (#4539)
* Escape leaves a Fill & Sign tool, as it leaves a comment tool

The first Escape closes the open type box, the next one goes back to
Select; Fill & Sign's Text tool otherwise opened a box at every click with
no way out by the keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fill & Sign: a new text box deselects, so two presses of Escape close it and leave the tool

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:13:56 +01:00
FredApps 9cc1736dc9 Enhance scanned file: decode bi-level images within the budget, DecodeParms resolved 2026-11-05 12:56:10 +01:00
FredApps 9927a1bd12 M10: page_enhance_scan as its own dispatch method
Main split Automation::call into per-tool methods and its clippy
cognitive-complexity limit (30) now fails with the inline arm.
2026-11-05 12:47:19 +01:00
FredApps 2f2d69be17 M10: merge main into feat/scan-clean; classify mixed scan pages in one pass
Merge origin/main. Conflicts: docs/changelog headers keep both histories;
edit/run.rs keeps EnhanceScan beside main's run_edit_page_image; optimize
images.rs keeps main's scaled() and the crate-visible jpeg(); the German
strings in ui-egui/src/i18n/de.tsv go with that file, which main moved to
craft-i18n. decode_samples now takes main's ImageBudget; a scan clean-up
run shares one sized by the file.

Review (CodeRabbit, scan_clean.rs): a page whose own images and a
scan-holding Form only together cover it now cleans the images and reports
the Form; a page whose images alone cover it reports its Form too. New
regression fails on the old classification.
2026-11-05 12:14:01 +01:00
Helen Kwok 3df903d516 pdfcraft-web: speak the host messages through craft-web-host
The transports, the message types and the origin checks come from the shared crate (storytold/craft-libs#420); PdfCraft keeps the save and dirty hooks, the open size limit and the author.
2026-11-05 17:51:00 +08:00
Helen Kwok c79e48d608 web host mode: refuse an oversized host file before copying it, cap the name, and document trust and limits 2026-11-05 17:08:15 +08:00
Helen Kwok 43efa9a9e3 packaging/web: say that ?host=parent takes precedence over data-host="page" 2026-11-05 17:08:15 +08:00
Helen Kwok 5574a8bf4a ROADMAP: a framed ?host=parent selects the parent bridge; only other requests keep downloads 2026-11-05 17:08:14 +08:00
Helen Kwok d5b8a35248 feat(web): page host mode, so a page can host PdfCraft in its own document (#4731)
data-host="page" on the canvas opts a page that loads the module into its own document into the host bridge. The ?host=parent messages travel as non-bubbling CustomEvents on the canvas; host::Bridge holds a Transport (Parent or Page) with one send, and save, dirty, ready and failure reporting are shared. ?host=parent is checked first and unchanged. Hosting guide documents it.
2026-11-05 17:08:14 +08:00
Helen Kwok 0f99608112 Contributors: credit helenkwok as Helen Kwok 2026-11-05 17:08:14 +08:00
863af864a7 M1: respect generations in PDF object references and full saves (#1143)
* M1: respect generations in COS object references and full saves

Resolve stale references to null instead of replacement objects with the
same number. Validate complete identities in writer readers, indirect
stream lengths and catalog checks, and keep generation-aware length caches.

Cover cache reuse, edit overlays, snapshots, incremental and packed/classic
full saves, and stream lengths with synthetic regressions and independent
hayro checks.

Co-Authored-By: Codex <noreply@openai.com>

* M0: clarify refreshed CI roadmap note

Co-authored-by: OpenAI Codex <noreply@openai.com>

* M1: repair contradictory xref generation references

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M1: cover repaired generations and freed-slot round trips

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: document generation repair and bounded diagnostics

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: record generation repair regression evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: document bounded COS generation repair

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: record generation repair parity evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* fix(cos): preserve repaired generations through preflight and saves

Keep accepted xref/header aliases across repair-aware edits and snapshots, canonicalize full-save identities, and retain bounded generation diagnostics from older revisions. Add COS, writer/encryption, and real preflight regression coverage.

* test(preflight): assert repaired aliases share saved identity

Address the review nitpick by comparing reopened HeaderImage and RowImage references after damaged full saves, for classic/packed output and edited/restored snapshots. Focused preflight regression and rustfmt check pass.

* M14: update certificate dependencies for RustSec advisories

Bump rustls-webpki to0.103.16 and webpki-root-certs to1.0.10. Preserve all other locked packages and dependency lists, manifests, Git pins, and advisory policy.

---------

Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 09:28:43 +01:00
alehelandClaude Opus 5.5 fdc9f646f9 fix(ui): a grouped double-click, held presses and blocked Add text, from review
CodeRabbit's findings on #6243, each checked against the code:

- A double-click on the objects selected together took its target from
  the hits at the release, so a release just off the paragraph pressed
  opened nothing. The router keeps what lay on top at the press with the
  objects aside (`under_objects`), as every owner acts where its press was.
- A drag first seen on another page left the picked picture picked: that
  page is told `Elsewhere`, and the picture's test no longer ran there.
- Delete with a button held removed the item, and the press held then named
  the next one. Delete waits for the button to come up, for pictures and
  added content.
- A page just off the screen is still run for what lies near it, so a
  picture's keys acted there unseen. They ask whether the page is on screen.
- Add text's drags end while its input is blocked, as Edit text's do.
- With the page gaps hidden, a Shift-click on the row two pages share
  selected on the first page and the second cleared it. A press another
  page holds leaves the selection alone.

Tests, each seen failing with its fix reverted:
`a_double_click_on_a_grouped_paragraph_released_just_off_it_opens_it`,
`a_drag_first_seen_on_the_next_page_lets_the_picked_picture_go`,
`delete_waits_for_the_button_to_come_up` (each half),
`a_selected_pictures_keys_wait_for_its_page_to_be_on_screen` (its
continuous case), `a_paragraph_drag_ends_when_the_window_loses_focus` (Add
text), `a_shift_click_where_two_pages_touch_keeps_what_it_selected`,
`a_press_counts_the_modifiers_of_its_own_event`.
`add_text_keeps_a_changed_paragraph_and_a_draft_when_it_moves_on` pins that a
click on the page applies the open paragraph before a new box starts, so
neither edit can take the other's place; seen failing with the draft's edit
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 08:51:44 +01:00
FredApps 15c505d1c6 Merge pull request #3858 from Kronbii/fix/scan-image-resampling
M2: scans stay sharp at screen size and smooth when zoomed (area-average shrink, bilinear enlarge)
2026-11-05 06:03:58 +01:00
FredApps 84e85c1ff3 Merge pull request #5758 from hasannadeem/feat/update-check-preference
M14: Preferences can turn off the update check at start (#5737, #5460)
2026-11-05 05:39:12 +01:00
FredApps f3dc867651 Merge remote-tracking branch 'origin/main' into w
# Conflicts:
#	vendor/README.md
2026-11-05 05:14:13 +01:00
FredApps 21540829cd Merge pull request #5754 from agent-reed/fix/fill-sign-quick-bar-controls
M5.7: the Fill & Sign text controls use a colour dot and a size flyout
2026-11-05 04:48:17 +01:00
FredAppsandClaude Opus 5.5 943718ea98 fix(ui): About credits tables fit without side-scrolling and their columns resize (#6052)
* fix(ui): About credits tables fit without side-scrolling and their columns resize

The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.

The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let every credits column be dragged, and refit changed tables

The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.

A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.

fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refit credits tables on what they measure, not the fitted widths

Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.

It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.

The table now compares the natural widths and the clip rect's width,
which scrolling does not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:33:21 +01:00
FredApps 13cb547434 Merge remote-tracking branch 'origin/main' into fix5754
# Conflicts:
#	crates/ui-egui/tests/it/fill_sign.rs
2026-11-05 04:06:56 +01:00
FredApps 713c61548f Merge pull request #5435 from agent-reed/feat/fill-sign-rail-default
M5.7: the quick bar's Fill & Sign button shows the tool it picks
2026-11-05 04:06:42 +01:00
Dylan M. Taylor d22641a875 Pass the Linux signing key to the release workflow (#6200) 2026-11-04 17:44:52 -05:00
FredApps 083b3aca44 Merge pull request #6087 from KenM76/feat/model3d-render
M12: model_render, a page's 3D model drawn to a PNG (stacked on #6069)
2026-11-04 20:19:35 +01:00
FredApps 52d68d7844 annot: share threed's MAX_MODEL_BYTES with model3d
main's 3D-embed change (#5808) added crates/annot/src/threed.rs with its own
pub MAX_MODEL_BYTES (same 256 MiB value), so both were re-exported from the
crate root and pdfcraft-annot no longer compiled after merging main. model3d
now uses threed's constant.
2026-11-04 19:52:51 +01:00
FredApps 697a02e2ef Merge storytold/main into feat/model3d-render
# Conflicts:
#	crates/automation/src/lib.rs
#	crates/automation/tests/it/main.rs
2026-11-04 19:52:51 +01:00
FredApps ef8c71ac9c Merge pull request #6069 from KenM76/feat/prc-render
M12: headless renderer for PRC models (`pdfcraft-prc-render`), stacked on #5786
2026-11-04 19:42:33 +01:00
FredApps 8f8842e3ab Merge pull request #5396 from KenM76/fix/ocr-edit-under-scan
fix(edit): an edited OCR phrase under its scan image is drawn on top (#3973)
2026-11-04 19:42:25 +01:00
alehelandClaude Opus 5.5 723667f29d test(ui): pin the behaviour changes no test covered
Found in review: the PR lists these and nothing pinned them.

- With Prepare open, a drag from a highlight draws the selection rectangle.
- A double-click on a paragraph selected with others opens it.
- In crop mode, a right-click on the next page goes to what lies there,
  though a clipped artwork's whole frame reaches over it.
- A crop grip takes a press within its reach, 9 px off.
- A comment's menu opens over a picture and over a paragraph.
- A right-click with the button held opens the selected comment's menu.

Each seen failing against a planted violation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 a21f098049 test(ui): true docs and no dead code after the review of the routing
Found in review. No change in behaviour.

- The guard's `PAGE_HIT_CALLS` refused nothing once `main`'s press-handler
  check went; it only failed when it was stale, so it goes.
- The guard's notes said `canvas/image_pick.rs` uses none of the marks (it
  takes `PagePointer` now), that page modules read only
  `PagePointer::drag_started` (some ask for the primary button themselves),
  and left out the point tests that tell a tool a press began on its page.
- `content_ui`'s and the comments' notes said added text is its own with
  every tool, and that added content decides for itself.
- `object_ui`'s `!double_clicked()` can't fail (the page view re-routes
  that press), and Fill & Sign's preview recomputed `hovered`.
- `Capture` is private, and `top` is `canvas`'s.
- Test notes gave as history bugs that only this branch ever had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 8102109d19 fix(ui): a right-button drag leaves a text selection alone
Found in review. `fix(ui): a right-button drag on the pages starts nothing
a left one does` covered drag starts. A right-button drag still moved the
end of a selection already made, as `update_dragged_selection` tested a
drag by any button (`main` replaced the selection instead). And its end
finished a selection whichever button it was, so with a markup tool, Replace
Text or Redact a selection left from another tool became a comment. Both
read the primary button now.

Tests, each seen failing with its fix reverted (Redact's on its own):
`a_right_button_drag_leaves_a_text_selection_alone`,
`a_right_button_drag_makes_no_mark_of_a_text_selection`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 9262213589 fix(ui): Add text's held draft and a locked document keep their limits
Found in review, each against `main`:

- With Add text holding text it can't draw, a drag from a paragraph moved
  the paragraph; only its click was refused. Neither acts now (#125's
  draft stays where it is being typed).
- A right-click reached a Prepare field whatever the field tool and while
  a table is set up (a review ruling), but also on a document that can't
  be changed, where Prepare stays open after a tab switch: it selected the
  field and opened a menu of entries the engine would refuse. It selects
  nothing there.

Tests, each seen failing with its fix reverted:
`add_text_moves_no_paragraph_while_it_holds_a_draft_it_cant_draw`,
`with_prepare_on_a_locked_document_a_right_click_on_a_field_selects_nothing`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 dd9ec46efc fix(ui): a picture's grip and knob take their drag from the press
Found in review. On `main` the selected picture's grips and rotate knob
were drag-only widgets of their own, so their drag began at the press.
Their widgets went, and the page's response is click-and-drag, which calls
it a drag only past egui's click distance: a move of a few pixels resized
nothing. They take their drag from the press again. A click that doesn't
move edits nothing, as before; a drag of the picture itself still waits,
since a click selects.

Tests: `a_short_drag_from_a_picture_grip_resizes_it` (seen failing with
the fix reverted), `a_click_on_a_picture_grip_changes_nothing` (a pin,
seen failing with the release planted to edit always).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 fd091beaab fix(ui): a drag from the selected picture moves it under another picture
Found in review. The selected picture's frame is its own on `main`: a
drag from it moves it, though another picture lies over it there. The
routed press was the top picture's, and the drag only started for the
selected one's, so nothing moved. `PicturePart::OverSelected` says so: a
click there selects the picture on top, as before, and a drag moves the
selected one.

Test, seen failing with the part planted away:
`a_drag_from_the_selected_picture_under_another_moves_the_selected_one`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 1ee332ae55 fix(ui): a picture's keys act only while its page is drawn
Found in review. Delete, Backspace and crop's Enter and Escape moved to
the app's keys, which run before anything is drawn and don't look at what
is on screen. So with Edit text and a picture selected:
- Delete in the page grid removed the hidden picture and kept the pages;
- Delete with the pointer on the Pages panel did the same;
- with the picture's page scrolled away or in single-page view on another
  page, Delete removed a picture the reader couldn't see, and Enter
  applied its crop.

`main` read these keys in the picture's own page pass, which none of
those cases reach. `picture_keys` runs there again, from
`page_tool_input`, under Edit text's gate (so no dialog gets them), and
before the group acts. Delete still works wherever on the pages the
pointer is.

Tests, each seen failing with the fix reverted:
`delete_in_the_page_grid_deletes_the_page_and_not_the_hidden_picture`,
`a_selected_pictures_keys_wait_for_its_page_to_be_on_screen`,
`delete_with_the_pointer_on_the_pages_panel_deletes_a_page`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 79205fe4ac fix(ui): with Edit text, what has no menu gives way to a picture under it
Found in review. With Edit text, a right-click on added text or on an
open paragraph's frame went to that, which has no menu: the picture under
it was unselected and the page's menu opened. `main` opens the picture's.
Those give way to a picture under them, as paragraphs do, and to nothing
else: a comment under added text stays unselected, as before.

A comment in `draw_pages` said a selected Prepare field keeps its menu
off the pages. Since the page menu follows the right-click, it stays
selected and the menu is the page's.

Tests, each seen failing with the fix reverted:
`a_right_click_on_added_text_over_a_picture_selects_the_picture`,
`a_right_click_on_an_open_paragraphs_frame_over_a_picture_selects_the_picture`.
`a_right_click_with_edit_text_on_added_text_over_a_comment_selects_no_comment`
is a pin, seen failing with added text dropped from the hits outright.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 a5ed8c68bf test(ui): the page guard sees router aliases and scoped names; true docs
Found in review:

- Rule (f) missed `pub(in …)` functions, the visibility the guard's commit brought
  in (`declares` strips any `pub(…)`), and flagged names that merely
  end in its words (`dropdowns`): it now matches whole words of the name.
- Inside `canvas` the narrowed visibility doesn't hold, and a router
  taken whole (`let r = &view.router;`) got past the checks by method
  name; such a use now counts as the router's (`takes_the_router`).
- Docs: the router's module doc and `PagePress::Free` say a declined
  drag is nobody's once it starts; Prepare's note says a highlight's drag
  is Prepare's; Add text's paragraph note names `ParagraphCx::adding`;
  a test's doc claimed crop-mode keys it doesn't press.
- One source each for the objects' menu test (`object_ui::menu_open`)
  and Edit text's gesture cancel (`cancel_pointer_gestures` calls
  `cancel_edit_text_gestures`); the page-menu test asserts an entry
  only the page's menu has.

Each guard addition seen failing against a violation planted in
`#[cfg(any())]` code in `canvas/area.rs`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 d10a89fdd1 fix(ui): Edit text's presses that 4b's routing dropped reach their owners
Found in review, each against `main`'s behaviour:

- A double-click on one of the objects selected together only left the
  group: the second press was the group's. It is the object's under it,
  so added text opens for typing and a picture is selected, as before;
  a paragraph opens too (the user's ruling).
- On a page whose objects can't be selected together, a click on the
  empty page left the picture selected: no area selection starts there
  to drop it, and the picture's own test that did is gone. A click nobody
  holds on the page unselects it.
- Crop mode's grips stopped at the page's edge, so those of a picture
  filling its page could be taken from their inner half only. They reach
  past it, cursor, press and right-click: no page took a right-click
  there, which left crop mode and opened the page menu (#5900 keeps crop
  mode there). The box and the picture stop at the page's edge, as a
  clipped artwork's whole frame can reach over the next pages.
- A press past a page's edge was the page's whenever it had any hit
  there: the objects selected together, or the open paragraph's frame,
  took presses on the next page and did nothing with them. Past the edge
  only the selected picture's knob and grips (crop mode's too) take a
  press, and the objects' hit stays on its page.
- A drag from a hole in the selected picture (a clip with a hole) fell
  through, started an area selection and unselected it. The selected
  picture's frame is its own, as before.
- Image pick: a drag or double-click from a link let the picked picture
  go before ruling 5, and does again; the link's click still picks none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 caf364670f fix(ui): Delete on a selected added picture deletes it alone
`refactor(ui): crop mode is a hit of its picture` moved the picture's Delete to the app's key handling, which
runs before the page view, so its edit applied before added content read
the keys. Added content's selection mirrors the picture's when the
picture is added content (`sync_image_selection`), and once the picture
was gone that index named the next added item on the page: added
content's own Delete, seeing the same key press, deleted it too.

The picture's Delete consumes the key, and drops the mirrored selection
with the picture's.

Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 f592fe332a fix(ui): a right-click goes to what its tool reaches, and the menu follows it
Found in review of the right-clicks 4b routes:

- With Edit text, a right-click on a picture under a link went to the
  link, which Edit text doesn't follow: the page menu opened and the
  picture was unselected. A right-click counts links only for the tools
  that reach them (Select; the Link tool).
- Edit text's paragraphs have no menu, and give way to the picture
  under them (the user's ruling): text over a picture, an OCR'd scan's
  among it, no longer hides the picture's menu.
- With a Prepare field tool, or while a table is set up, Prepare's hit
  leaves fields out, so a right-click on the selected field opened the
  page menu. A right-click reaches fields whatever the field tool.
- In crop mode, a right-click off the crop went to crop mode's whole-page
  hit and unselected every picture. Only one on the picture being
  cropped, its box or its grips is the crop's (#5900's area), which
  opens no menu; elsewhere it goes to what lies there, as without crop
  mode: another picture is selected for its menu.
- Under a held press, the menu was the page's on anything but a link. It
  is the routed owner's when that owner is the one selected (the held
  press leaves the selections alone), the page's otherwise.

`DocView.menu_for` is `pub(in crate::canvas)`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 c10f87f673 fix(ui): a comment's gesture whose release the pages missed applies nothing
`fix(ui): a comment's move, resize or reshape ends` ended a comment's move, resize or reshape once the button was
up, and applied it where the pointer then was: when the pages missed
the release (the page scrolled out of range, a tab switch, a tool
switched mid-drag), the comment jumped to wherever the pointer next
showed up over its page, and while the pointer stayed off the pages the
gesture waited for it.

Only a drag that stops on the page applies its gesture now; one that
ends with the button up and no drag stopped there ends with nothing
applied, whether the pointer is on the page or not. The test hovers away
from the move's start, where the jump shows.

Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 58fa110d2d fix(ui): a right-button drag on the pages starts nothing a left one does
The pages' presses are the primary button's (`PagePointer.edges`, the
router's `began`), so a right-button drag was a press nobody held, and
every tool that started on egui's `drag_started()`, which counts every
button, started on it: the Rectangle tool drew a rectangle, Prepare's
selection rectangle selected the fields in it, the Link tool opened Link
Properties for a new link, the camera moved its quadrilateral to the page,
and the Select tool selected text. On `main` too.

`PagePointer.drag_started` is a primary drag of a press the pages hold,
started this frame. The page modules read it instead of egui's (comments'
shapes, callout and text box, Prepare, the Link tool, the camera, the text
layer, image pick, and the Edit text group, whose owned presses were
primary already); zoom, crop, Sign area and Redact already asked for the
primary button. The guard refuses a bare `.drag_started()` in a page
module (`NOT_PAGE_DRAGS` lists the widgets of their own: Organize's
thumbnails, the signature pad, the Scan tab), and the router's
`drag_started` is `drag_began`, so the rule reads cleanly.

Tests, one per family, each seen failing with the fix reverted:
`a_right_button_drag_draws_no_rectangle`, `…_selects_no_field`,
`…_draws_no_link`, `…_leaves_the_camera_quadrilateral`,
`…_selects_no_text`; the guard rule against a planted `#[cfg(any())]`
call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 3c0ac1f77c test(ui): wait_for_cursor waits with step_until
It hovered fifty times with a 10 ms sleep between, so a busy runner
could run out of tries before the page's text or paragraph boxes
arrived from their worker threads. It now steps frames with
`support::step_until`, as the other waits for background work do,
until the cursor is the one wanted or 30 seconds pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:47 +01:00
alehelandClaude Opus 5.5 0378eee687 test(ui): page modules never read the router; the compiler holds it
With the Edit text group, the text layer and link following told what
the press is to their page, only the page view reads the router.

- The router's methods and `DocView.router` are `pub(in crate::canvas)`:
  outside `canvas` the compiler refuses a read or a write, aliases
  included, which the guard's text match could miss. `ROUTER_READERS`
  keeps only the page view's dispatcher (`page_tool_input`).
- Rule (f): no function in a page module is named with "grabs" or
  "owns", which decide for themselves whether a press is theirs.
- CLAUDE.md's "Page presses" rule says so: modules never read the
  router; they are told.

Each rule seen failing against a violation planted in `#[cfg(any())]`
code (a `router.keeps_drag()` read in `content_ui`, a `fn fill_grabs` in
`fill_sign`), and a real router read from `content_ui` no longer
compiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:46 +01:00
alehelandClaude Opus 5.5 b3509dc629 fix(ui): a comment's move, resize or reshape ends once its button is up
A comment's gesture ended only on egui's `drag_stopped()`. When the pages
missed its release (the button let go where they didn't see it), the
gesture stayed live: it kept the page's frames from Fill & Sign and the
shape tools, and the next drag that stopped on the page committed it,
taking the comment there.

It now ends, as the other page gestures do, on `drag_stopped()` or once
the primary button is up, where the pointer is.

Two tests planted a live move with the button up to check that a live
move keeps its page's presses (`main`'s
`active_comment_gesture_keeps_highlighted_link_click` and 4a's
`a_fill_and_sign_click_places_nothing_while_a_mark_is_moved`). That
state now ends at once, so both hold the press through the planted move
instead: the intent stands, and each still fails with the live-move
guard taken out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:46 +01:00
alehelandClaude Opus 5.5 85fbcc436b refactor(ui): the text layer, image pick and link following are told the press
The text layer asked the router whether the press's owner kept its drag,
link following asked it which link held the press, and the Link tool
started a new link from a press any link held, since the router kept a
declined drag's capture.

- `PageRouter::drag_started`: a drag the owner declines (text markup;
  with the Link tool, a link) is nobody's once it starts, on the page it
  began on. The Link tool draws a new link only from a drag nobody
  holds.
- `PageInput` carries the press (`PagePress`) and whether its drag is the
  page's text's (`text_drag`): the text layer and link following read
  them, and `PageRouter::owner` goes. Only the page view reads the
  router now.
- Image pick acts on a press nobody holds: a link's click follows the
  link only, and picks no picture under it (the user's ruling).
- The I-beam drops over a link only where a press would go to it, the
  top hit under the pointer (the user's ruling): the tools that select a
  link's text show it there.
- `PageText::glyph_at` is the one test for the text under a point, for
  the text layer and Redact's boxes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:37:46 +01:00