Commit Graph
1105 Commits
Author SHA1 Message Date
aiongg 5def4a8c01 Story Optical Margin Alignment with a size, and Ignore Optical Margin (Story panel, engine and IDML) (#1705)
* Type: story Optical Margin Alignment with a size, and Ignore Optical Margin (engine, IDML)

A story's Optical Margin Alignment applies to every paragraph except those
with Ignore Optical Margin; hang amounts scale with the story's size.
story.options sets it, type.para and paragraph styles take
ignoreOpticalMargin. IDML imports and exports StoryPreference
OpticalMarginAlignment/OpticalMarginSize and IgnoreEdgeAlignment. The
per-paragraph opticalMargin switch keeps hanging at the text's own size.

Part of #947.

* Type: the Story panel, and Ignore Optical Margin in the Paragraph panel menu and Paragraph Style Options

Window > Type & Tables > Story sets Optical Margin Alignment and its size
for the stories of the text selection or the selected text frames through
story.options, and is disabled without either. type.selectionAttrs
reports the stories' values as storyOptions, null where they differ.
The Paragraph panel menu toggles ignoreOpticalMargin, and Paragraph Style
Options > Indents and Spacing has an Ignore Optical Margin checkbox.

Part of #947.

* ROADMAP: the story Optical Margin Alignment entry names its default and scope
2026-10-24 10:08:40 -04:00
aiongg d767fcf41e Text frames: a custom baseline grid's Relative To (page, margin, frame or inset) (#1758) 2026-10-24 09:55:47 -04:00
aiongg d4c1322d97 Merge pull request #1709 from aiongg/w65-batch
Integration batch: book-chapter IDML baseline grids, hyphenation, and the Paragraph panel stack
2026-10-24 08:14:29 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 8271f1a26b refactor(engine): split execute_unguarded, load_styles and resize_border (Sonar S3776) (#1726)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:50:14 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 8299d26400 chore: replace trivial closures with method paths (Sonar S1612) (#1714)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:30:43 -04:00
Dylan M. TaylorandDylan Taylor f90d286721 chore: resolve SonarCloud maintainability issues (#1698)
* chore: resolve SonarCloud maintainability issues

- Replace lambda closures with method references (S1612)
- Replace wildcard imports with explicit imports (S2208)
- Refactor complex functions with extracted helpers (S3776)
- Replace OR pattern with range pattern in math parser (S9047)

* style: apply rustfmt to warichu helper

* fix(compose): revert slice conversion in tests and remove unused var in warichu

* Group warichu row layout scalars to satisfy S107 parameter limit

* style: satisfy rustfmt in warichu.rs

---------

Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-24 03:54:24 -04:00
skylar taylor-barrick 00d5fa5cdd M6: reject unreadable PDF trailer history links (#1670)
Reject a non-null Prev entry that cannot be read as an offset instead of silently treating it as the end of the guarded trailer chain. Keep absent/null end markers and readable numeric offsets unchanged.

Reproduced CodeRabbit discussion_r4366245854 with an independent probe and a failing native regression before the fix. The synthetic regression covers seven unreadable value types plus absent/null and integer/real history controls.

Validation: all 9 cargo xtask ci gates passed (3063 tests passed, 6 skipped; 16 WASM-safe crates). One unrelated GPU test was labelled leaky by nextest in that run and passed cleanly in an isolated rerun. Independent checks: 17 original supported PDF variants still pass qpdf, logical preservation and Poppler pixels; 6 original refusal cases remain refused. All 11 additional Prev controls pass (7 refused, 4 accepted).
2026-10-24 00:40:33 -06:00
skylar taylor-barrick 7427a15863 M6: harden placed PDF layer variants (#1670)
Integrate current main (d8d78851) without rewriting the contributor's commits, retaining craft-libs 9f75d3dc.

Accept literal null trailer entries while rejecting actual or ambiguous encryption/hybrid markers. Structurally inspect bounded xref/trailer history instead of adopting trailer-looking comments. Preflight all selected source variants before editing/undo; preserve reset-to-defaults. Suppress stale unrewritable canvas overrides rather than revealing source content. Keep conservative all-content crop fallback without overcropping layerless/null stubs.

Validation: cargo xtask ci, all 9 gates passed; 3062 tests passed, 6 skipped; all 16 WASM-safe crates checked. 17 supported variants passed independent qpdf, logical preservation and Poppler pixel checks; 6 unsupported/repair-only cases refused. Direct-harness Claude Opus 5 reviewed the exact main-relative delta with no blocking findings.

No native-format changes or unrelated layer features. Synthetic regression fixtures are generated in test code.
2026-10-24 00:18:22 -06:00
aiongg c428083d74 Merge #1483: Bulleted and Numbered List buttons give InDesign's default hanging indent
# Conflicts:
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/cs.rs
#	crates/ui-egui/src/i18n/de.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/ka.rs
#	crates/ui-egui/src/i18n/pl.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/ru.rs
#	crates/ui-egui/src/i18n/uk.rs
#	docs/target-app-parity.md
2026-10-24 06:00:59 +00:00
aiongg 87be33e969 Hyphenation: new documents start with Capitalized Words, Last Word and Across Column off 2026-10-24 05:56:36 +00:00
aiongg d8d7885172 Anchored objects: Relative to Spine mirrors custom positions on right-hand pages (#1702)
* Anchored objects: Relative to Spine mirrors custom positions on right-hand pages

* Anchored objects: page side from the frame's spread position; footnote cache keyed by page side
2026-10-24 01:34:05 -04:00
aiongg 3b71900097 Links: resolve stored paths with craft-docpath's resolve_absolute (#1399)
* Links: use resolve_absolute for stored paths

* Links: check for a missing linked file with is_ok_and

* CI: release only with git dependencies from storytold

The winit patch from skylartaylor/winit at its pinned rev is the one other source allowed.

* Links: Relink to Folder only uses a name that stays a file name in the folder

* CI: the git-sources check reads Cargo.lock as TOML, needs it current and refuses config patches

* CI: the git-sources check parses .cargo/config and .cargo/config.toml for source replacement, patches and paths

* Links: rustfmt

* Links: Relink to Folder only takes a file directly in the chosen folder

* Links: Relink to Folder compares folders as the file system resolves them
2026-10-23 21:19:50 -04:00
Brandon Thomas aaa18ad1c2 Merge pull request #1550 from Silvertree2010/fix/h1233-text-frame-column-count-is-trunc
Clamp text frame column count before narrowing to u32
2026-10-23 17:29:35 -04:00
Brandon Thomas 8eb4f5590a Merge storytold/main into feat/cell-rotation-reported
# Conflicts:
#	crates/idml/src/tests.rs
2026-10-23 17:29:22 -04:00
Brandon Thomas 17d197dbef Merge pull request #1567 from Hamza0101/fix/insert-pages-count
A page count Insert Pages cannot mean is refused
2026-10-23 17:29:02 -04:00
Brandon Thomas 3e783b2671 Merge pull request #1562 from Hamza0101/fix/duplicate-page-in-export-list
A page named twice in an export list is exported once
2026-10-23 17:28:54 -04:00
Brandon Thomas 515796aa1c Merge pull request #1571 from Hamza0101/fix/unique-anchor-name
A text anchor's name is the document's, so it is not reused
2026-10-23 17:28:50 -04:00
Brandon Thomas 132a0816e1 Merge pull request #1376 from Silvertree2010/fix/h869-table-alternating-fill-counts-ar
Saturate table alternating-fill and index paragraph counts
2026-10-23 17:28:43 -04:00
Brandon Thomas dd9ab4b6d9 Merge pull request #1369 from Silvertree2010/fix/h850-polygon-side-count-is-truncated
Clamp polygon side count before narrowing to u32
2026-10-23 17:28:24 -04:00
Brandon Thomas abda81d473 Merge pull request #1661 from marcoarnulfo/fix/align-key-object
Align: a click on one of several selected objects makes it the key object
2026-10-23 17:28:06 -04:00
Brandon Thomas f0907d3831 Merge pull request #1665 from MarekSpodBiedry/issue-1624
find: search and change text inside table cells in document scope (#1624)
2026-10-23 17:27:58 -04:00
Brandon Thomas 597fc5c900 Merge pull request #1574 from Hamza0101/fix/character-size-bounds
A type size outside 0.1 to 1296 pt is refused
2026-10-23 17:27:18 -04:00
Brandon Thomas dc968ecdff Merge pull request #1629 from marcoarnulfo/feat/autoflow-existing-pages
Autoflow: fill the pages that follow before adding pages at the end
2026-10-23 17:26:42 -04:00
Brandon ThomasandClaude Opus 5.5 1e86a669ca table: the rotation comment names only the IDML import warning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:18:56 -04:00
Brandon ThomasandClaude Opus 5.5 253be57a85 layout.pages.insert: range-check the count before narrowing to usize
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:18:46 -04:00
Brandon ThomasandClaude Opus 5.5 50161d0d42 Index: next-paragraph ranges saturate and read the paragraph list with get
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:15:22 -04:00
Brandon ThomasandClaude Opus 5.5 2aa2ef4b7c find: move the table id into the cell iterator (E0373)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:02:49 -04:00
aiongg 2c32ee389b Engine: Export PDF and Print take a creation date, so the print test's two exports match (#1680) 2026-10-23 15:38:43 -04:00
andeecollard da8d3a2937 PDF: accept forward xref links and reject cycles 2026-10-23 19:19:20 +01:00
Marco Arnulfo 85552ca0f3 Selection: a key object needs at least two selected objects 2026-10-23 19:03:26 +02:00
andeecollard 62a26d9e35 PDF: preserve catalogs and full references in layer variants 2026-10-23 17:47:18 +01:00
MarekSpodBiedry 708feab5cd find: search and change text inside table cells in document scope (#1624) 2026-10-23 17:37:45 +02:00
Marco Arnulfo 8eab33eba1 Align: no key object once the selection is down to one object (#1637) 2026-10-23 16:54:21 +02:00
Marco Arnulfo c764454ed3 Align: a click on one of several selected objects makes it the key object (fixes #1637) 2026-10-23 16:22:52 +02:00
Dylan M. Taylor bd7fb76d4c fix: annotate package output permissions (SonarCloud rust:S2612)
New packages are created with the ordinary 0666 request; the process umask
decides the effective mode. Annotated with that reason so the analyzer's
world-writable warning does not change the intended sharing behaviour.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-23 09:53:28 -04:00
Marco Arnulfo 2caa39e130 Autoflow: fill the pages that follow before adding pages at the end
Autoflow (Shift-click, autoflow on Place, text.thread with autoflow) inserted
new pages right after the story's last frame, in front of the document's own
pages. As in InDesign, it now threads one frame per column on each page that
follows, whatever else the page holds, and only then adds pages at the end of
the document. It fills whole pages, so an estimate that stops inside a page
can't leave its later columns out of the thread. Frames it set past the end
of the text are removed again; the pages they were on stay. Smart Text Reflow
of the primary text frame keeps adding pages after the story's last page.

Shrinks gap 26: flowing into the primary text frame of the following pages
is left.
2026-10-22 23:28:22 +02:00
moppeeerandClaude Opus 5.5 b3896d8122 List buttons: track the hanging indent they set (listHang)
A paragraph attribute records that the Bulleted or Numbered List button set
the hanging indent; turning the list off takes the indent away only then
(and while it is still as applied), and clears the mark. A paragraph whose
own indents were 18/-18 before the list keeps them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:30:14 +02:00
aiongg d2bdb0866f Paste: other apps' text goes into a text caret after copying objects (#1344)
* Paste: other apps' text goes into a text caret even after copying objects (#212)

* Paste: text that matches a copied frame still pastes; groups with text frames aren't anchored (#212)
2026-10-22 09:26:43 -04:00
aiongg 22a40fd57e Merge pull request #1144 from aiongg/land-402-404
Land the rebased #402 and #404
2026-10-22 09:26:38 -04:00
moppeeerandClaude Opus 5.5 99c47da919 List buttons: a selection ending at a paragraph start leaves it out; a style hanging indent stays
A selection ending right after a return (a whole paragraph, as a triple
click selects it) applies the list to that paragraph only, and the button
shows pressed for it; a whole frame still takes in its last, empty
paragraph. Turning a list off clears only a hanging indent set on the
paragraph itself: one the paragraph style gives stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:07:06 +02:00
Hamza 80cf719555 A type size outside 0.1 to 1296 pt is refused
Nothing checked type size. `type.char {"size": 0}` set type with no size at
all, a negative size went in the same way, and 1e9 pt asked the composer for a
line a million times the height of the page — all of it silent, and all of it
saved to the document.

The range is the one this engine already applies to a ruby annotation's size,
0.1 to 1296 pt, which is InDesign's: "size 0: 0.1 to 1296 pt".

It sits in `validate_char_fill`'s place in the chain, so every command that
takes character attributes gets it: `type.char`, `find.change`, the four
`style.character` / `style.paragraph` create and edit commands, and the two
table paths that already validated a fill.

The test covers five refused sizes, the three in range including both ends, and
two of the style commands. Without the check, `{"size": 0}` applies again.

Not from a ticket: found probing `type.char`.
2026-10-22 17:12:15 +05:00
Hamza c212c89bab A text anchor's name is the document's, so it is not reused
`anchor.create` took any name, including one another anchor already had, so a
document could hold two anchors called "Photo 1". Every reference to the name
then means whichever the search finds first: `xref.insert {"anchor": "Photo 1"}`
and `hyperlink.create {"anchor": "Photo 1"}` take the first, and an export
writes both destinations under the one name. A name nobody can resolve is not a
name.

A name already in the document is a usage error now, naming it and where to
look: "a text anchor named `Photo 1` is already in this document (anchor.list
names them)". A name that is empty or only spaces is refused too — a
cross-reference built from `<txtAnchrName />` would show nothing.

Case still distinguishes names, as it does elsewhere in the document, and
nothing about existing anchors changes: the check is on creation.

The test covers the same name in the same story and in another one, the empty
names, that only one anchor exists afterwards, and that another name and a
name differing in case are both fine. Without the check the duplicate is
created again.

Not from a ticket: found probing `anchor.create`.
2026-10-22 17:00:05 +05:00
Hamza 2946f2af62 A page count Insert Pages cannot mean is refused
Found probing `layout.pages.insert` on a two-page document:

    {"count": 0}      → one page inserted  (0 clamped up to 1)
    {"count": -1}     → one page           (`as_u64` fails, the default applies)
    {"count": 1.5}    → one page           (the same way)
    {"count": 10000}  → 9999 pages, quietly capped

A count that is given is a number of pages, so each of those is a usage error
now: "count 0: 1 to 9999 pages". A count that is absent still means one page.

`after` is left alone. My first attempt also refused a position past the last
page, and `insert_after_u64_max_appends_at_the_end` caught it: appending is
what an out-of-range `after` is meant to do, and the test now says so in the
same place.

The test covers the five counts, that none of them inserted anything, that a
position past the end still appends, and that `{"count": 2, "after": 0}` still
inserts two pages after the first. Without the check, `{"count": 0}` inserts a
page again.

Not from a ticket: I found it probing the command's parameters.
2026-10-22 16:41:11 +05:00
Hamza e7ef29d9d1 A page named twice in an export list is exported once (found while probing)
The same selection written two ways gave different files:

    file.exportPdf {"pages": "2,2"}   → 1 page
    file.exportPdf {"pages": [2, 2]}  → 2 pages, the second a copy
    file.exportPdf {"pages": [2,2,2]} → 3 pages

`parse_page_range` keeps the first of a repeat (`"2-2,2"` is one page too); the
array form appended whatever it was given. A list says which pages to export,
not how many times, so the array now drops a repeat as the string does. Both
`file.exportPdf` and `file.exportPptx` read the same parameter.

The test pins the agreement between the two forms over nine lists, in PDF and
PPTX, and the two facts I measured while checking it: the pages come out in the
document's order whichever order the list names them in, and `[2, 2]` is now
byte for byte the file `"2"` gives. A page that does not exist is still
refused, and an empty list still exports nothing.

Not from a ticket: I found it probing the page-range parameter.
2026-10-22 16:18:33 +05:00
Hamza a473793c07 Rotated cell text is settable and reported, not silently flattened (#1468)
A cell's text rotation (Cell Options › Text › Rotation) was already read from
IDML and written back, but only 0 is composed, so an imported form's narrow
header columns of vertical labels came in horizontal and wrapped mid-word
("Eingela / ngt am") — and nothing anywhere said why. The reporter's file has
30 such cells.

Composing rotated cell text is a larger change than this. What this does is
stop it being silent, and let the value be set from the API:

- the IDML import reports it once per file: "2 table cell(s) have rotated text
  (Cell Options › Text › Rotation): the angle is kept and written back, but the
  text is composed horizontally";
- `table.setCell` takes `rotation` (0, 90, 180 or 270, any other angle refused,
  negatives and multiples normalised), which nothing could set before;
- `table.get` reports each cell's rotation, so a round trip can be checked.

Tests: the angle through an export and back with the warning counted once for
the file (and no warning when no cell is rotated), and the command's accepted
and refused angles. Each fails without its own part.
2026-10-22 15:59:33 +05:00
moppeeer f21c6994e5 Merge remote-tracking branch 'origin/main' into list-buttons-hanging-indent
# Conflicts:
#	ROADMAP.md
2026-10-22 11:20:13 +02:00
NetterSilverandClaude Opus 5.5 35e5b39874 Clamp text frame column count before narrowing to u32
Found while testing crates/engine/src/cmd/object.rs:2519

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 08:40:18 +00:00
moppeeer fc04020f0b Merge remote-tracking branch 'origin/main' into list-buttons-hanging-indent
# Conflicts:
#	ROADMAP.md
2026-10-22 09:07:49 +02:00
aionggandHamza 333f2a04e3 Selection: drag a column guide to move its gutter (#1096) (#1176)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: test a ruler guide in a column gutter without facing pages (#1096)

* View: Lock Guides and Lock Column Guides are commands (#1096)

view.lockGuides {on?} (Cmd+Alt+;) sets the Lock Guides UI state the Properties panel and ruler-guide drags use. view.lockColumnGuides {on?} is a document setting, on by default, saved with the document, not an undo step, and read from and written to IDML ColumnGuideLocked. Both are in View > Grids & Guides with checkmarks.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* Layout: Margins and Columns keeps dragged columns unless the count or gutter changes (#1096)

* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)

* Integration: close #1121's round-trip test beside the ruler-guide test

* View: Lock Column Guides is translated wherever Lock Guides is (#1096)

* View: Lock Column Guides is translated wherever Lock Guides is (#1096)

* Layout: a new margin or page width resets dragged columns (#1096)

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* Layout: #1121's margin case follows the new-width rule (#1096)

* View: Lock Column Guides is undoable (#1096)

* Layout: dragged columns scale with a new margin or page width (#1096)

* Layout: dragged columns mirror when a page changes side (#1096)

* Layout: the page-width test drags a page whose columns are its own (#1096)

* Selection: drag a column guide to move its gutter (#1096)

* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)

* Selection: column-guide drag review fixes (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 02:52:17 -04:00
aionggandHamza a912438ab8 Layout: autoflow, inserted pages and new parents follow dragged columns, mirrored across sides (#1096) (#1173)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* Layout: dragged columns mirror when a page changes side (#1096)

* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 02:26:38 -04:00