Commit Graph
4870 Commits
Author SHA1 Message Date
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
vangel76andClaude Sonnet 5.5 0346b49502 Merge upstream/main into feat/optimize-target-bytes
Keeps main's reworded mono-image text and the target_bytes paragraph in the doc_optimize description.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 01:27:49 +01:00
vangel76andClaude Sonnet 5.5 7c443590e1 Merge upstream/main into feat/crop-toolbar-5330
Crop pages moves into the hotbar's core tool buttons, where main now builds the quick bar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 01:27:05 +01:00
vangel76andClaude Sonnet 5.5 a371b24f2a test(sign): tighten the NeedAppearances tests (#5939)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 01:25:41 +01:00
vangel76andClaude Sonnet 5.5 a62633e902 fix(sign): the first signature drops /NeedAppearances so Acrobat recognises it (#5939)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 01:16:17 +01:00
vangel76andClaude Sonnet 5.5 c4dd5fe245 Format
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:07:26 +01:00
vangel76andClaude Sonnet 5.5 d604dc3c30 Merge upstream/main into perf/write-full-retain-parsed-objects
Upstream now counts the parsed tree and keys in held_bytes and tracks the cache total itself,
which replaces this branch's copy of the cache accounting and its owned_bytes. write_full's
retention budget uses held_bytes; the separate node budget is dropped since held_bytes counts nodes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:06:23 +01:00
Dylan M. Taylor d22641a875 Pass the Linux signing key to the release workflow (#6200) 2026-11-04 17:44:52 -05:00
oracola91 a427cd2de5 Word takes the script a language tag names
pa-Arab-PK is Punjabi in Arabic script, which Word keeps as right-to-left
text, but word_lang looked only at the language and wrote w:val. A script
subtag (Arab, Hebr, Syrc, Thaa; Hans, Hant, Hani, Jpan, Kore, Hang, Hira,
Kana; Latn, Cyrl, Grek) now decides over the language. It is read only in
its own place, after the language and any extended languages, so private
use such as en-x-arab names no script.
2026-11-04 23:00:11 +02:00
oracola91 561cc0d707 A language tag can't end in a singleton
A singleton (one letter or digit) starts an extension, or with x private
use, and BCP 47 needs a subtag after it. en-x and en-a were written as the
document's language. They and a singleton straight after another are now
left out; one-character subtags inside private use stay allowed.
2026-11-04 23:00:11 +02:00
oracola91 0cae1c518b A language tag longer than 35 characters is still a tag
BCP 47 sets no maximum length; 35 characters is only the least a protocol
must hold. A well formed tag such as en-US-u-ca-gregory-nu-latn-co-phonebk
was left out of both exports. The bound is now 255, kept only so a PDF's
/Lang can't put an unbounded string in the file.
2026-11-04 23:00:11 +02:00
oracola91 a699e13305 Word and HTML exports name the document's language
Word export named no language, so Word checked the spelling of every
export as English and underlined a whole document in any other
language. The PDF already says its language in the catalog's /Lang.
When that is a well formed language tag, Word gets it as the default
language of the text (w:docDefaults), in the attribute Word keeps for
its script: w:eastAsia for Chinese, Japanese and Korean, w:bidi for
Arabic, Hebrew, Persian and Urdu, w:val for the rest. Both HTML
exports, with and without CSS, get <html lang>. Without /Lang, or with a
malformed one, the files are exactly as before.

docx_with_language, html_with_language and html_simple_with_language
take the language; docx, html and html_simple stay as they are and call
them with none. The engine reads /Lang through the existing initial
view.

The new export and engine tests fail without the change.
2026-11-04 23:00:11 +02:00
KenM76andClaude Opus 5.5 ecb5cae460 Merge origin/main (083b3aca) into feat/rich-copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 15:54:54 -05:00
KenM76andClaude Opus 5.5 29ea66cd4e M3: the clip text-cap test is bounded, so it fails without the cap
The test pushed until the builder refused; with the cap removed it never
stopped. It now pushes at most two chunks past the cap and checks the cap
stopped it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 15:54:28 -05:00
KenM76andClaude Opus 5.5 a955373428 M3: copy with formatting keeps one clipboard open and is tested through a stand-in
Review fixes for Copy with formatting.

- ui-egui: the HTML copy goes through one arboard Clipboard, created on
  first use and kept in the app (os_clipboard::with_kept, under the same
  process-wide lock), instead of a fresh one per copy. On X11 the copied
  data is served only while its Clipboard lives, so a fresh one dropped
  at once could leave nothing to paste without a clipboard manager. A
  failed create or set falls back to egui's plain-text copy.
- ui-egui: html_clipboard_override stands in for the system clipboard in
  tests. With the system clipboard on, Copy sets HTML and plain text in
  one set and emits no CopyText; when the set is refused it emits
  CopyText with the plain text. The HTML is built only when a clipboard
  will take it (system clipboard on, not the web build). The
  last_copied_html field is gone.
- export: clipboard HTML text is escaped in one pass and also drops C1
  controls, a bare carriage return and the bidirectional overrides and
  isolates. Font sizes are written within 1 to 1000 pt.
- engine: the glyph-to-block step budget is a parameter, tested; the copy
  says styles_incomplete only when matching actually ran out of steps.
- automation: text_extract format "html" has one 32 MiB HTML budget
  across all pages; a page past it gets none, and the result carries
  html_truncated. The tool description says so.
- parity: the new tests are evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 15:51:57 -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
alehelandClaude Opus 5.5 a9f592fd64 fix(ui): Edit text's drags end when its input is blocked
Edit text ignores input while a dialog, the palette, another window or
another tab has it. Only a drag of the objects selected together ended
then; a paragraph's move or rewrap, a picture's move, resize, turn or
crop and added content's move waited, and once the input came back with
the button up they landed wherever the pointer was.

While Edit text's input is blocked, `DocView::cancel_edit_text_gestures`
abandons them all, as the objects' already was.

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