upstream/main c3859c82 (#614e198eb5d, #6159509b2bc, #6168e151a2f, #617c3859c82).
Conflict: ROADMAP.md: header keeps next's change list plus orderly native exit recovery; progress log both
Conflict: apps/designcraft-cli/src/main.rs: next's case-insensitive .jpg/.jpeg check with main's write_atomic
Conflict: apps/designcraft/src/main.rs: on_exit keeps next's startup.presented() (not an Option) and adds preserve_documents_on_exit; next's discard_marked_text and main's preserve_documents_on_exit both; Services write uses write_atomic and keeps next's clipboard_text; tests both
Conflict: crates/engine/src/cmd/book.rs: main's ChapterBatch paginate with next's chapter-number resolution and {pages, chapters} result and doc
Conflict: crates/engine/src/cmd/file.rs: revert takes main's discard_document ordering without the revision + 1 (next's DocState::new hands out a fresh revision)
Conflict: crates/engine/src/recovery.rs: main's save_tracked, write_entry and open (st.bump_revision() for next's global revisions); next's reopen-twice test assertions kept
Conflict: docs/gaps.md, docs/target-app-parity.md: headers combined; next's Language, Books and Numbering lines plus main's book update safety line
Conflict: crates/engine/src/cmd/object.rs: object.fit's doc keeps next's text and adds the `axis` clause; test modules from both sides
Conflict: crates/ui-egui/src/canvas.rs: kept next's input code (it sends the second press as Down with clicks: 2) and dropped the PR's release-time DoubleClick; the PR's UI test stays
Conflict: crates/tools/src/select.rs: the handle double-click joins next's second-press preamble, after the port checks and before editing text and the frame/content toggle (Down or DoubleClick with click_count 2 on a handle runs object.fit along fitting_axis); the DoubleClick arm stays empty; module doc from both
Conflict: crates/tools/src/tests.rs, ROADMAP.md: both sides
Conflict: crates/engine/src/cmd/file.rs: the spec keeps next's pdfCrop boxes and loadCursor with the PR's width bound and placement precedence; the PR validates `width` earlier, so next's later want_w line goes; next's loadCursor branch stays
color.sample takes `matchFrom`: with no opaque image pixel under the point (an empty part of an image frame, a transparent pixel), it applies that object's attributes as object.matchAttributes does, in the same command; the tool passes the object under the pointer. sample_item and the tool walk shown_children(), so only a multi-state object's active state is sampled, and sample_item skips points outside the graphic's clipping path. object.matchAttributes's body is a function both commands share.
Conflict: crates/engine/src/cmd/style.rs: object.color keeps next's apply_color, spec and enabled check (#84); takes the PR's color.sample (with next's enabled check), image_color_at and sample_item; drops the PR's color_swatch, color_items and has_text_or_selection; explicit `id`/`ids` win over a text selection in apply_color; the PR's tests follow next's caret behaviour (a caret takes the sampled colour for the next typing)
Conflict: crates/tools/src/tests.rs: both sides; the eyedropper test's Asset and Graphic literals gain next's pdf_crop, pdf_box, clip and wrap fields
Conflict: crates/ui-egui/src/i18n.rs, i18n/{ar,it,ja,pt_br,uk}.rs, ROADMAP.md: both sides
Conflict: crates/engine/src/tooling.rs: every with_ctx caller (IME preedit, commit, end, caret) takes the PR's `|s, cx| s.tool.…` form; pointer keeps next's end_composition before dispatch and tool_key its composing() early return
Conflict: crates/ui-egui/src/canvas.rs: the cursor block takes `space` from navigation_space, then the zoom branch, then `(space && !cmd) || alt_text`; the spring-zoom pointer events carry next's `clicks`; the PR's tests name their button helper `press` (next's tests have a `primary`) and compare cursors with what cursors::show sets for the engine cursor (next paints or rasterizes cursors per platform)
Conflict: ROADMAP.md, docs/target-app-parity.md, docs/ui-parity.md: status lines list both sides; progress and change-log rows keep both
The update rebases the compact panel next has (already inside the control bar's scroll area) and adds accessibility labels and the progress line.
Conflict: crates/ui-egui/src/panels/mod.rs: next's swatch_list with the PR's swatch_picker returning the chip's response; next's swatch-list test discards it
Conflict: crates/ui-egui/src/chrome.rs: kept next's control-bar tests (they find the Fill chip by its group)
Conflict: crates/ui-egui/src/{lib.rs,object_control.rs}: rebased duplicates; kept next's side, then took the PR's labels
Conflict: ROADMAP.md: both progress lines
Conflict: crates/engine/src/cmd/layout.rs: Document Setup keeps next's refusal of an out-of-range start page; layout.section's spec keeps next's styles (kanji) with the PR's 1–99999 bound; the section body takes the PR's SectionEdit, whose start number goes through next's start_number check; tests keep next's section_start_numbers_are_bounded with the PR's one-undo-step test (the PR's start_numbers_are_bounded expects Document Setup to clamp, which next refuses, so it is left out)
Conflict: crates/doc/src/lib.rs: kept next's iterative saturating page_number; dropped the PR's second MAX_PAGE_NUMBER (next has it in page.rs)
Conflict: crates/ui-egui/src/dialogs.rs: next already leaves Enter and Escape to an open popup; kept next's side and added the PR's Style-menu key test
Conflict: crates/engine/src/cmd/mod.rs, crates/ui-egui/src/{i18n.rs,i18n/it.rs}: rebased duplicates; kept next's side
The update rebases the engine commits next has and adds the progress docs.
Conflict: crates/engine/src/cmd/{book,layout}.rs: rebased duplicates; kept next's side
Conflict: docs/target-app-parity.md: next's Language line (Norwegian) with the PR's Books line
The update rebases the tint fix next already has (60bf43b9, with the known-swatch check combined).
Conflict: crates/engine/src/cmd/object.rs, crates/ui-egui/src/{i18n.rs,i18n/it.rs,panels/properties.rs}: kept next's side
The PR's cut-per-column fix and its test duplicate next's equivalent (4f16d883); the merge records the head.
Conflict: crates/compose/src/lib.rs, crates/compose/src/tests.rs: kept next's code and tests
The update is a restack on #536 and #537 (whose selected-text fix next already has); #536's rebased head comes in as its base.
Conflict: crates/engine/src/cmd/text.rs, crates/fonts/src/{fontdb,lib}.rs, crates/ui-egui/src/{i18n,lib}.rs, i18n/it.rs, panels/font_menu.rs: rebased duplicates of PR code next has; kept next's side
Conflict: crates/engine/src/cmd/text.rs: kept next's selection_attrs (typing format, `mixed`) and took the PR's selected_text_preview for the selected text
Checkpoint the latest document state during App::on_exit after honoring current quit approvals. Keep incomplete or stale plans, clear recovery for clean documents and report unavailable or failed storage.
Follow-up to #545; cancelable macOS Dock Quit remains open in #544.
PR #617.
Conflict: crates/ui-egui/src/i18n.rs, i18n/{ar,it,ja,pt_br,uk}.rs, panels/properties.rs: rebased duplicate of the PR code next has; kept next's side and took the new pasted-into-frame fix
Conflict: ROADMAP.md: dropped the rebased duplicate progress line
Conflict: crates/engine/src/cmd/file.rs: spec strings keep next's (extension, return value, .indd); Save a Copy adds the extension then rebases data-source paths on a copy; file_save takes main's prepare-then-commit order with next's bump_revision
Conflict: ROADMAP.md: status line lists both changes; progress log keeps both sides
Keep recovered snapshots available through a second crash before autosave. Track checkpoint ownership across folders and restarted IDs; Save, Close and Revert clean only checkpoints owned by that document.
Follow-up to #506 and the retention concern in #380.
PR #616.
Prepare and stage every chapter before a book update writes files. Preserve imported IDML sources, reject open native chapters and report completed paths after a later write failure.
Follow-up to #485.
PR #615.
Route desktop writes and CLI raster exports through the engine atomic writer. Add actual-caller regressions for reader visibility, private modes, symlinks and valid long filenames.
Follow-up to #485.
PR #614.
Preserve document paths, revisions and collection contents when persistence fails. Rebase Save a Copy paths without changing the open document.
Follow-up to #485.
PR #613.
gaps.md gap 11, typography-parity.md (Tabs row), target-app-parity.md (typography row) and
ui-parity.md (panels row) described the Tabs panel as missing.
an_image_contour_stops_at_its_frame builds its Graphic with next's clip and wrap fields.
a_wrap_object_cutting_a_narrow_frame_leaves_its_sliver_empty and lines_in_band test a line's
band from its top to its baseline. New test: a Detect Edges clipping path overhanging its frame
wraps text (Same As Clipping) only within the frame.
Conflict: crates/compose/src/lib.rs: wrap_exclusion keeps next's custom boundary and outline closure, which now yields (outline, clipping frame) and limits the contour to the frame via Contour::within
Conflict: crates/compose/src/lib.rs: contour_path keeps next's (it, kind) form; Same As Clipping (the graphic's clip path) and Bounding Box (shown bounds) return the frame path as the clip, other kinds the frame with no clip
Conflict: crates/compose/src/lib.rs: main loop and estimate_slots keep next's grid and wrap_slot code; wrap_slot drops its desc argument and tests the band from the line top to its baseline (free_slots and the in-band edge search), for both callers
same_para_style_spacing uses deserialize_explicit_value, so Some(None) round-trips through
serde. Test: SameParaStyleSpacing as a Properties element on the based-on Normal style closes
the gap between consecutive headings; SetIgnore keeps the usual spacing.
Builds the desktop app and the CLI from source with `nix build`, and offers
packages, an overlay, `nix run`, a dev shell and a formatting check.
- Cargo.lock drives the dependencies (git sources via allowBuiltinFetchGit).
- Installs the desktop file, hicolor icons, MIME types and AppStream metadata
from packaging/linux/, validated at build time.
- Wraps the binaries so the dlopen'd libraries (libwayland-client,
libxkbcommon(-x11), X11, Vulkan loader, libEGL, libdbus for the portal file
chooser) are found on NixOS: native Wayland on GNOME and Hyprland, X11 as
fallback.
- Fonts from storytold/craft-fonts are pinned to the release workflow's revision
where the app embeds them.
- doCheck is off (the suite needs corpora and a GPU); `nix build` was verified.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflict: crates/compose/src/lib.rs: same-style gap replaces the pending space; otherwise next's text_above span space-before rule
Conflict: crates/doc/src/attrs.rs: same_para_style_spacing before the drop-cap fields and their doc
Conflict: crates/compose/src/tests.rs: both sides' tests
Conflict: crates/idml/src/tests.rs: both sides' tests
Conflict: crates/engine/src/atomic.rs: main's side (the reviewed #485 code supersedes the PR head on next)
Conflict: crates/engine/src/recovery.rs: main's #506 code, with next's write_entry helper kept
Conflict: apps/designcraft/src/profile.rs: main's own_run returning Option (and_then), with next's portable root_with argument
Conflict: crates/ui-egui/src/lib.rs: main's QuitState quit flow (d020b4e8) over the #545 head; next's Find Font alert guard; one import_warning_tests module (next's, with the Find Font test)
Conflict: apps/designcraft/src/native_menu.rs: next's MAC_APP_MENU layout, which already has the dc-quit item running app.quit
Conflict: ROADMAP.md: next's header with the quit change added; next's log entries then main's three
Conflict: docs/target-app-parity.md: main's atomic save, recovery and quit lines
Review follow-up.
- OK ran layout.section and then layout.chapterNumbering: two undo steps,
and a chapter start the second command refused (over 99 999) left the
section applied. `layout.numberingOptions {page, …section, chapter}`
checks both parts before the document changes and applies them in one
edit; the window's OK runs it. layout.section and
layout.chapterNumbering share its parsing.
- Enter or Escape with a Style menu open confirmed or closed the whole
dialog. Dialogs now leave both keys to an open popup (read before the
dialog draws, since egui closes a popup on Escape that frame).
- A start page of 4294967295 overflowed page_number (a panic in debug
builds, page 0 in release). Page numbers saturate; layout.section
refuses start numbers outside 1-99 999 (MAX_PAGE_NUMBER, as chapter
numbers) and Document Setup clamps its start page instead of wrapping
a 64-bit number into 32 bits.
Also rebased on main: the restructured ROADMAP keeps main's text, and the
inventory in docs/target-app-parity.md lists chapter numbering and the
window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Layout > Numbering & Section Options now opens its own window instead of
the generated command form, on the section of the page in view: Start
Section (always on for page 1), Automatic Page Numbering or Start Page
Numbering at, and a Page Numbering group with Section Prefix, Style,
Section Marker and Include Prefix when Numbering Pages; then Document
Chapter Numbering with its style, Automatic Chapter Numbering, Start
Chapter Numbering at, Same as Previous Document in the Book (only in a
book) and the book's name. OK and Cancel sit beside the options, out of
the scrolling body.
OK runs layout.section and layout.chapterNumbering; a refused value
(a prefix over eight characters or with + or a comma, a start number
below 1) keeps the window open and changes nothing. Opening the window
reads the chapter numbering without running a command, so it doesn't
stop a Data Merge preview. New strings are translated (German, French,
Spanish, Japanese, Chinese, Italian).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paragraph range inside a tracked change takes the change into its character
attributes, so the mark holds for its character ranges however their format is
built.
A column, frame or page break character that tracked changes delete, or a
hidden condition hides, no longer sends the next paragraph on. Deleted
paragraphs between a live break and the next paragraph don't stop the break.
Tests cover a deleted paragraph range inside a change, a deleted frame break in
both nestings, and a live break before a deleted paragraph.
Put the compact object groups inside the Control bar's existing sideways
scroll area, and give the painted captions (X:/Y:/W:/H:, icon captions,
Columns) and the Fill/Stroke chips accessible labels so tests and screen
readers find them. The Fill and Stroke chips open with nothing selected,
as before, so a popup opens at a scrolled control.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebased on the progress-docs restructure: the ROADMAP bullet moved to the
inventory in docs/target-app-parity.md (Books), with a progress-log line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A section prefix is at most eight characters and contains no plus or
comma (page references and ranges use them); `layout.section` refuses
others and leaves the section unchanged. Symbols, a footnote style,
no longer number pages. The parameter documentation lists the 01, 001
and 0001 styles, and a test pins the fields the generated dialogs read
from both numbering commands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A document's chapter number now has a style (1, I, i, A, a, 01, 001,
0001) and a source, as in Numbering & Section Options > Document
Chapter Numbering: Automatic Chapter Numbering (one more than the
previous document in the book; 1 first), Start Chapter Numbering at,
or Same as Previous Document in the Book.
`layout.chapterNumbering {style?, start?, source?}` sets them (undoable)
and, with no parameters, reports them with the open book that holds the
document. The book's Update Numbering (`book.paginate`) resolves chapter
numbers in book order and reports them. Chapter Number variables and
cross-reference chapter numbers use the style.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changing the Tint in the Fill menu sent the first object's swatch with
it, so with several objects selected every fill became that swatch; and
a Stroke tint on a frame without a stroke gave it the default 1 pt black
stroke.
object.fill now takes a tint alone and keeps each object's swatch, and
object.stroke with only a tint leaves a [None] stroke alone; the menu
sends just the tint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InDesign's Fill and Stroke menus in Properties › Appearance have a Tint
field and, for a text frame, Apply To: Frame or Text. DesignCraft's
listed only the swatches, so a tint needed the Swatches panel and a
text frame's text colour needed a text selection.
The menus now have both: the tint applies to the fill or stroke of the
frame (object.fill / object.stroke) or of its text (type.char fillTint /
strokeTint); Apply To: Text colours the whole story. With text selected
the menu applies to the selected text, with its tint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>