Commit Graph
1709 Commits
Author SHA1 Message Date
Codex 7f8b352196 Merge main and retain shortcut and preflight documentation 2026-11-12 18:13:30 +01:00
FredApps a5be0905dd Merge pull request #2649 from dotCooCoo/fix/merge-image-fields
Data merge: an image field in text places the image, not its path
2026-11-12 18:05:02 +01:00
FredApps 9aa103b3a8 Merge pull request #1843 from dotCooCoo/fix/copied-story-anchor-table-ids
Copy: copied text gets text anchors and tables of its own
2026-11-12 18:04:28 +01:00
FredApps 31d4c7f7f4 Merge pull request #2665 from dotCooCoo/feat/paste-clipboard-image
Paste: images on the system clipboard paste as embedded images
2026-11-12 18:03:55 +01:00
FredApps e8276337a3 Merge pull request #2354 from dotCooCoo/fix/table-story-target
Reject ambiguous table IDs across stories
2026-11-12 18:02:30 +01:00
FredApps 6e50ee3974 Merge pull request #2650 from dotCooCoo/feat/paragraph-value-checks
Text: paragraph spacing and justification values are checked
2026-11-12 17:57:35 +01:00
FredApps 2522b78ef3 Merge pull request #2656 from dotCooCoo/fix/preflight-off-interface-thread
Preflight: check the document on a background worker, not on every edit
2026-11-12 17:56:38 +01:00
dotCooCoo 7604db695f Copy: point copied cross-references at copied anchors in one pass
Each copied story with text anchors walked every story copied before it
to retarget their cross-references, so copying many such frames took
time quadratic in their number. The copies' cross-references are now
pointed at the copied anchors once, after the last item is copied.
2026-11-12 07:57:27 -08:00
dotCooCoo 329806477e Data merge: an explicit QR role in text is refused, and the caret follows each field
data.placeholder.add with role qr and the insertion point in text bound
the QR code to the first selected frame, which the caret was not in. It
now returns "qr placeholders bind to a frame", as a QR field there does.

A field inserted at the insertion point left the caret before its
marker, so a second field went in front of the first. The caret now
moves past the marker, as after typing.

Docs: docs/agents.md.
2026-11-12 07:45:10 -08:00
dotCooCoo c2a1543528 Copy: mark the renumber results as must-use
renumber and its helpers return false when a copy nests deeper than
MAX_COPY_DEPTH and must not be inserted. They now carry #[must_use], so
a caller that drops the result fails to build under -D warnings.
2026-11-12 07:40:29 -08:00
Adithya Shankaran 92043b4c48 Automation roots: refuse COM¹ to LPT³ and a device name before a colon (#2383)
* Automation roots: refuse COM¹ to LPT³ and a device name before a colon

is_device_name missed COM¹, COM², COM³ and LPT¹, LPT², LPT³, which Windows reserves as it does COM1.
It also cut the name only at a period, so COM1: and NUL:x were not seen as device names.

* Credit my name in the About window

* Automation roots: document the device-name tests

CodeRabbit's docstring check counts the touched tests. same_component stays without a doc comment, because #2205 deletes that function and a doc line above it conflicts.

* Automation roots: record the device-name refusal

The MCP roots description and the roadmap now name COM¹ to LPT³ and a device name before a colon.
2026-11-12 15:09:39 +00:00
dotCooCoo fad00dd646 Data merge: an image field in a table cell or a note is refused by name
With the insertion point in a table cell or a note, data.placeholder.add
with an image field returned "a hyperlink field can't go in a table cell
or a note". It now returns a message about image fields that names the
two ways on: bind the field to a frame, or pass role text to merge its
path. Nothing is inserted on the error, and role text still puts the
marker in the cell.

Docs: docs/agents.md and the command's params text.
2026-11-12 05:58:02 -08:00
dotCooCoo 375a7b48b1 Merge main into fix/merge-image-fields
# Conflicts:
#	crates/engine/src/cmd/datamerge.rs
2026-11-12 05:21:21 -08:00
autocraft-bot[bot] 424d08d935 Merge main into fix/datamerge-fields-in-tables 2026-11-12 11:38:19 +00:00
Dylan M. Taylor 027bbae9cb Engine: index style, tag, swatch and run lookups; bound polygon sides, Change All and spelling candidates
* fix(engine): index lookups that were quadratic in a document's styles, tags, swatches and runs

* address review: first swatch per name in book sync; bound spelling input before collecting

* address review: keep the last source style per name in book sync; bound GREP replacement bytes while generating them

* address review: write GREP replacements into a byte-bounded buffer; gate keep_last test off wasm

* merge main into fix/sec-engine-linear-lookups

* address review: make the suggestion length-cap test deterministic
2026-11-12 06:28:15 -05:00
Dylan M. Taylorandautocraft-bot[bot] ff0463e299 Exports: leave out text the page doesn't show (inherited conditions, deletions, hidden layers)
* fix(export): leave out text hidden by inherited conditions or deletion in every exporter

* address review: skip hidden frames in page text and hidden article items; first visible paragraph as nav label; read table anchor's own format

* address review: skip nonprinting frames and non-printable layers in fixed-layout page text

* Apply review fixes

- crates/epub/src/html.rs
- crates/engine/src/cmd/export/formats.rs

* Apply review fixes

- crates/epub/src/html.rs
- crates/engine/src/cmd/export/formats.rs

* address review: fixed-layout page text skips frames in inactive object states

* address review: place a threaded story at its first shown frame; skip it only when every frame is hidden

---------

Co-authored-by: autocraft-bot[bot] <354215646+autocraft-bot[bot]@users.noreply.github.com>
2026-11-12 06:28:07 -05:00
Dylan M. Taylorandautocraft-bot[bot] b7174dc92d Engine: bounded DTD validation, compound-path hole orientation
* dtd: bounded matching steps, element depth and problem count

* address review: stop DTD matching when a step charge fails; refuse hole orientation past its budget

* Apply review fixes

- crates/engine/src/dtd.rs

---------

Co-authored-by: autocraft-bot[bot] <354215646+autocraft-bot[bot]@users.noreply.github.com>
2026-11-12 06:27:59 -05:00
Dylan M. Taylor 26d5c8941c IDML: story export and object-style overlay in linear time, embedded image budget
* idml: story runs and marker offsets found once, object-style overlay by position, embedded image budget

* address review: saturating embedded-bytes counter

* address review: IDML export past the embed limit succeeds and warns for each image written as a link

* address review: an unlinked image is always embedded, only linked ones fall back to their link
2026-11-12 06:27:41 -05:00
Dylan M. Taylor a41fc046f3 Data merge: a record range stops expanding once the limit is met
* Data merge: a record range stops expanding once the limit or the record count is met
2026-11-12 06:27:35 -05:00
dotCooCoo ed9980fec2 Paste: params text gives the pasted image's size as one point per pixel
edit.pasteImage sizes a new frame as File › Place does, which reads a
raster at one point per pixel and does not read a stored resolution.
The params text and the comment said otherwise.
2026-11-12 00:39:11 -08:00
dotCooCoo b1a514fc51 Paste: ui.paste checks the image's size before decoding it
ui.paste {image} refuses base64 longer than the 256 MB file limit
before it decodes the data, with the same check edit.pasteImage uses.
2026-11-11 22:05:16 -08:00
dotCooCoo 8463cef4a9 Paste: refuse damaged clipboard images and journal one entry per paste
- edit.pasteImage decodes the whole raster before it stores the image,
  so a file whose pixels are cut off after a whole header is refused.
- edit.pasteImage is the only journal entry for a paste; the file.place
  it runs no longer adds a second one, so a replay places the image once.
- Empty clipboard text beside an image counts as no text, so Edit >
  Paste pastes the image.
- With a native menu, a pasted image event is ignored while the
  clipboard still holds what it held when objects were copied here.
2026-11-11 21:59:10 -08:00
.CooCoo 6fff5328eb Tests: type.align refusal cases name the check that refuses each (#2662)
Follows #2645. Its test said a misspelled key used to apply Align Left.
Only empty params and a bare string did; the parameter check already
refused a misspelled key, and the value parser refused an unknown
value. Each case now asserts the refusal message it gets.
2026-11-12 04:52:41 +00:00
dotCooCoo 630b5dfc43 Paste: images on the system clipboard paste as embedded images (#2204)
Paste ignored a bitmap on the system clipboard: a screenshot or an image
copied in a browser had to be saved and placed. Paste now places it.

- edit.pasteImage {base64, name?, spread?, at?} embeds the image in the
  selected empty frame, or in a new frame centered on `at` at its stored
  resolution (72 ppi when none) and at most 60% of the page width, as
  File > Place sizes it. Save and reopen and IDML export keep the pixels.
  Images over 64 million pixels and names without an image extension are
  refused.
- Ctrl/Cmd+V pastes the image egui delivers with no text, and Edit >
  Paste reads the clipboard's bitmap, or the first image file of a copied
  file list, when there is no text or the text is only an address.
- The menu read waits at most 1.5 s on its own thread, with one read in
  flight at a time.
- With a native menu, objects copied here still paste as objects until
  the pasteboard's change count shows a newer copy.
- ui.paste takes {image} for the control channel.
2026-11-11 20:50:24 -08:00
dotCooCoo 9cea62b238 Type: a repeated alignment records no undo step; menu-bar inserts stop with the window's shortcuts
- Align Left, Center, Right, Justify Left, Justify All Lines and
  type.align leave the document and the undo list alone when every
  target paragraph already has that alignment as its own value.
- On the Mac, Em Space, En Space and Current Page Number from the menu
  bar also do nothing while Quick Apply or a dialog is open, an input
  method is composing or a canvas context menu is open, the cases that
  stop the window's own shortcuts.
- Tests for both, and for Justify All Lines across threaded frames,
  over a paragraph style, through IDML export and import, and with
  parameters it does not take.
2026-11-11 20:48:43 -08:00
dotCooCoo b73c21db37 Data merge: a file that is not an image does not hide a later image candidate
The image search stopped at the first file that could be read and
checked whether it decodes afterwards. A non-image file at an earlier
candidate (the drive root, or beside the data file) then hid a valid
image beside the data file or the document, and the field was reported
missing. The decode check now runs inside the search, so only an image
ends it.
2026-11-11 20:36:39 -08:00
dotCooCoo fc09aa9124 Merge main into feat/type-shortcuts 2026-11-11 20:23:58 -08:00
dotCooCoo 099b89f53d Preflight: check the document on a background worker, not on every edit
The status bar's error count ran the whole-document preflight check on
the interface thread for every new state of the document, so every drag
step and every typed character waited for it. The Preflight panel ran
the same check on every frame. The check composes every story and asks
the file system about every linked graphic, so its cost grows with the
document: on a 300-page document with 900 placed images it took 137 ms
on a fast machine, and moving or resizing a frame and typing became
unusable on large documents.

The check now runs on a worker thread that takes the newest state of
the document; the issues last found stay shown until it answers. An
unchanged document is checked again every few seconds for linked files
changed outside the app, a save under a new path or a font reload is
checked again, and a check blocked on another document's linked file
is handed off to a fresh worker after 200 ms. Tests and the web build
check on the interface thread, once per state of the document.

preflight::check_document takes the document, the composition cache and
the save path, so the worker needs no session.
2026-11-11 18:53:44 -08:00
dotCooCoo 7809f5e9da Text: frame columns, gutter, switches and indents are checked too
object.textFrameOptions refuses a gutter outside 0 to 15552 pt, a column
count outside 1 to 40 (it was clamped) and an on/off option that is not
true or false (it was skipped). The Control bar's Columns field stops at
40, as the other column fields do. The params text gives the ranges and
lists flexibleWidth.

type.para and paragraph style create and edit refuse left and right
indents outside 0 to 15552 pt and first and last line indents outside
-15552 to 15552 pt. A style made from the selection is checked with the
values it takes from the selection.

style.paragraph.apply and style.paragraph.redefine check that Minimum,
Desired and Maximum stay in order in each justification row they change,
over the overrides paragraphs keep, and refuse the change otherwise.
2026-11-11 18:42:32 -08:00
dotCooCoo d490e0fb18 Data merge: a QR field at a text insertion point is refused instead of written as text
Insert Field with a QR column and the text cursor in a frame added a text placeholder, so a merge wrote the QR payload as plain text. It now returns "qr placeholders bind to a frame" and leaves the story unchanged.
2026-11-11 18:14:15 -08:00
dotCooCoo e90517be45 Text: paragraph spacing and justification values are checked
type.para, style.paragraph.create and style.paragraph.edit accepted any
number for paragraph spacing and justification. They now refuse Space
Before, Space After and same-style spacing outside 0 to 15552 pt, word
spacing outside 0% to 1000%, letter spacing outside -100% to 500%, glyph
scaling outside 50% to 200%, Auto Leading outside 0% to 500%, and a
Single Word Justification other than left, center, right or
fullyJustified. Each justification row needs Minimum <= Desired <=
Maximum over the values the paragraph or style resolves to after the
change, including the styles based on an edited style and the
paragraphs, table cells and notes that use them.

object.textFrameOptions refuses an inset outside 0 to 15552 pt or a list
that is not four sides, and a verticalJustification, firstBaseline,
autoSize or columnsKind that is not one of its names. These were applied
or skipped without an error before. IDML import reads these values as
before.
2026-11-11 17:51:19 -08:00
dotCooCoo e362820bc4 Data merge: an image field in text places the image, not its path
An image field inserted at a text insertion point, or typed there as
<<name>>, merged as text, so each record showed the image's path.
It now merges as an inline image at its natural size, scaled down to
fit the smallest text column of the story's frames. A missing file is
listed in missingImages and its marker is removed. An explicit image
role at a text insertion point places an inline image too.

An image path that starts with one separator (\Photos\a.jpg) is tried
from the drive's root first, then beside the data file and the
document.
2026-11-11 17:42:24 -08:00
.CooCoo b284052acc Type: type.align without a value is an error (#2645)
type.align with no `align` key applied Align Left, so a call that
misspelled the key, or passed a bare string, changed the paragraphs to
left alignment instead of failing. The command now returns an error,
leaves the text unchanged and records no undo step. A named value still
applies, and `align: null` still removes the override.
2026-11-12 01:30:21 +00:00
dotCooCoo 9f4bc29c3e Tables: a malformed table id is refused with nothing in a table selected
With nothing in a table selected, a table command given a `table` that is not a nonnegative integer (`null`, a string, a negative number) was refused as disabled, since the enablement check read it as no table. It is now refused with "`table` must be a nonnegative integer", as it is with a table selected.
2026-11-11 13:31:04 -08:00
Dylan M. Taylorandaiongg 5a3507510e File reads: refuse devices and pipes, cap whole-file reads (#2227)
* fix(engine): whole-file reads take regular files only, up to the format limit

* Apply review fixes

- crates/engine/src/io.rs

* Apply review fixes

- crates/engine/src/io.rs

* Fix failing CI check

- crates/engine/src/io.rs

* address review: refuse to save an encoded document longer than the open limit

* address review: open files for reading without blocking (FIFOs, stream-backed pseudo-files)

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-11-11 16:19:45 -05:00
Dylan M. Taylorandaiongg 8d59590449 Package: copy each font as it was loaded, check read roots, number duplicate names in one pass (#2208)
* fix(engine): package fonts from their loaded bytes under the read roots

* address review: a font's own file is neither overwritten nor packaged when it changed since loading

* Apply review fixes

- crates/engine/src/cmd/package.rs

* address review: compare packaged font file in fixed-size chunks

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-11-11 16:19:20 -05:00
Dylan M. Taylorandaiongg cfd99ba9b5 Automation roots: compare paths exactly; recovery reads only regular entry files (#2205)
* fix(engine): exact root containment and regular-file recovery entries

* Apply review fixes

- crates/engine/src/recovery.rs

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-11-11 16:18:55 -05:00
Dylan M. Taylor eba5b9f845 datamerge: all further pages in one insertion, page lookups by start index (#2279) 2026-11-11 16:18:07 -05:00
Dylan M. Taylorandautocraft-bot[bot] 589f5ff8a3 Engine: Erase and Smooth search each distinct drag sample once (#2272)
* engine: Erase/Smooth look at each distinct drag sample once, thinned past 4096

* Apply review fixes

- crates/engine/src/cmd/anchors.rs

* address review: decide whether a drag touched the path on every distinct sample, then thin

---------

Co-authored-by: autocraft-bot[bot] <354215646+autocraft-bot[bot]@users.noreply.github.com>
2026-11-11 16:16:54 -05:00
dotCooCoo a7cd84849e Merge main into fix/table-story-target 2026-11-11 12:29:22 -08:00
.CooCoo 513f77b88e Tables: commands given a table id run with nothing in a table selected (#2607)
* Tables: commands given a table id run with nothing in a table selected

Table commands whose params list `story?` and `table?` (table.insertRow,
table.deleteColumn, table.options, the Convert Rows commands and the
others) were refused with "place the insertion point in a table" when the
call named the table but the selection was not in one. A call that passes
`table` now acts on that table whatever is selected. A table that is not
in the document, or not in the given `story`, is refused, and a `story`
that is not a non-negative integer is an error.

The menus are unchanged: with nothing in a table selected, the table
items stay disabled, and calls that name no table are still refused.

* Tables: a bad story is refused whether or not a table is selected

A call that names a table with a story that is not a non-negative integer (a string, a negative number, a fraction or null) was refused only when nothing in a table was selected. With a table selected, the command ignored the story and acted on the named table. Both paths now read story with one helper and refuse it the same way.

* Tables: an edit to a named table keeps a caret in footnote text

With the caret in footnote text, a table command that names a table
runs, and the edit cleared the caret's footnote address, so the next
typing went into the story. clamp_selection now leaves footnote
addresses alone, as in_cell does.
2026-11-11 19:56:37 +00:00
Dylan M. Taylor edd2f24bb0 address review: cap whole-reference strings and the resolved parameters of a step; range adjustment uses the inserted length 2026-11-11 05:11:14 +00:00
Dylan M. Taylor c85f7425a4 address review: build unvar value only when an instance exists; check interpolation length before copying; bound the limit-error preview 2026-11-10 21:48:57 -05:00
aiongg ef82d650db Fix failing CI check
- crates/engine/src/script.rs
2026-11-10 21:47:43 -05:00
aiongg 95acc4d47e Apply review fixes
- crates/engine/src/cmd/variables.rs
2026-11-10 21:47:43 -05:00
aiongg d0683224ca Apply review fixes
- crates/engine/src/cmd/variables.rs
- crates/engine/src/cmd/variables/tests.rs
- crates/engine/src/script.rs
2026-11-10 21:47:43 -05:00
Dylan M. Taylor e8f162ea9f fix(engine): end runaway loops in variable deletion, data-merge markers and nested scripts 2026-11-10 21:45:28 -05:00
skylar taylor-barrick c2211e0f41 Merge pull request #1161 from y-az/feat/anchored-text-frames
Anchored text frames: own stories and ids through copy, paste, duplicate, release and delete
2026-11-10 20:48:08 +01:00
9da88149be Fix Links panel stalls on network paths (#1444)
* Fix Links panel stalls on network paths

* Fix Links review findings

* Fix Links worker gating and wasm build

A link check blocked on another document's unreachable share no longer
holds back the document now shown: after 200 ms a fresh worker takes
over (at most four alive at once) and the stale result is dropped.

relinked_asset used the native-only fingerprint_from, breaking the
wasm build; it now uses fingerprint_of. The EPS relink and IDML link
reach tests build Windows paths correctly.

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

* Fix Links worker repaint and graphic read retry

Switching documents before the 200 ms takeover now schedules a repaint
for it: a blocked worker never asks for one, so without other input the
panel kept waiting. A failed graphic read is no longer cached as
missing, so the next check reads again. The legacy EPS fixture clears
its source fingerprint, as documents from before fingerprints have
none, so the test no longer depends on running within two seconds.

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

* Keep touched links OK and poll Links within seconds

Only a new size now marks a graphic Modified from its fingerprint. A
file with the same size but a new time (saved again, copied, restored
or checked out) is compared byte for byte, once per new time, and stays
OK when unchanged.

External changes are polled every two seconds instead of every five
minutes. The next check waits at least as long as the last one took
(up to five minutes), so a slow share stays idle half the time.

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

* Wake Links when a worker slot frees; translate check strings

A worker thread asks for a repaint when it ends, so a document waiting
at the four-worker limit gets a worker as soon as a superseded one
returns from its blocked call, without other input.

Rows from a check keep a weak reference to their document. The key
names it by address, which a later document can't reuse while the
allocation is kept.

The Links panel's new strings ("Checking", "Checking links…", "Link
status is unavailable.") use the UI's ellipsis and are translated in
every interface language. The comments on Session::text_link_checks and
on the byte comparison in links::status describe what they do now.

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

* Links: rows from the document, statuses from a check that can stop

The panel lists links from the document on the UI thread (no file is
looked at) and fills in the statuses a worker check last found, kept
per document. An edit shows at once, and a check of an earlier edit
still supplies statuses instead of being discarded.

- A check of another document is told to stop and ends before its next
  file; only one blocked in a file system call is replaced, so blocked
  workers no longer hold their slots for a whole document.
- Checks are timed on the worker, and what they remember passes to the
  worker that replaces them.
- A fingerprint is kept only for a file whose time was settled when it
  was read, as for linked text. A file found unchanged by its bytes
  gets its current size and time stored with the document
  (remember_link_files), without marking a saved document as changed.
- Asset::set_link keeps link and fingerprint together; copies made by
  Package and Copy Links To carry none.
- The Session holds one LinkListChecks. Update Link lists files only
  when a linked story was edited in the layout.
- The unused "Checking links..." caption is removed.

Co-Authored-By: Claude Fable 5.2 <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-10 17:39:39 +01:00
kuseidos bb54c44ad9 Command API: refuse a page index past the end, and say how many pages went (#2460)
* Command API: refuse a page index past the end, and say how many pages went

layout.pages.delete answered ok for an index past the last page and deleted nothing for it, so a caller off by one removed a different range and read a success; on a 336-page book that is page 337 instead of 336. Both commands now check every index against the document and refuse it with the sentence the MCP export commands have used since #70, return how many pages they touched so a no-op and a success are told apart, and state in their params that the indices count from 0.

* Pages: count the pages a delete will remove, not the indices it was given

delete_pages refused [0, 0, 1, 2] on a four-page document as if it would leave none, because the guard measured the length of the list instead of the pages it names. Same family as the index check next to it: the count was of the wrong thing. A test in the engine pins it.

* Pages: refuse a malformed page list instead of dropping what it cannot read

Review finding: page_indices used filter_map, so a list holding a string, a fraction or nothing at all was silently shortened — the caller was told a page was handled when it never was, which is the same mistake the command now refuses one level down. Every entry must be a whole number, and pages must be a list. Tests cover all three shapes, and the new test functions carry docstrings so the coverage check has nothing to report.
2026-11-09 04:05:22 +00:00