Commit Graph
142 Commits
Author SHA1 Message Date
aiongg 3d3f02e621 Merge save-result: fix(file): saving never reports success without a file (#741, #708) 2026-10-13 14:27:03 +00:00
aiongg eb83d23040 fix(file): saving never reports success without a file (#741, #708)
to_bytes returns a Result and every caller propagates it, so a document
that can't be written is an error: the existing file, the unsaved state
and the recovery checkpoint stay. Asset entries near or over 4 GiB are
written with zip64 sizes. On the web, Save marks the document saved only
after the browser download has started.
2026-10-13 14:22:27 +00:00
aiongg 73af5d7f63 Merge script-dollar: GREP replacements like $1 stay literal in scripts (#749)
# Conflicts:
#	ROADMAP.md
2026-10-13 13:01:31 +00:00
aiongg d227ee84e9 fix(script): only a well-formed $N reference is replaced; $$ is a literal $ (#749) 2026-10-13 13:00:34 +00:00
aiongg 605b31c6d6 Merge pr/663: CLI: accept a UTF-8 BOM in scripts and document the syntax in script --help (#663) 2026-10-12 23:56:41 +00:00
Marco Arnulfo 9e15d5a17e fix(cli): accept a UTF-8 BOM in scripts and document the syntax in script --help (fixes #654) 2026-10-13 00:41:46 +02:00
skylar taylor-barrick 64f0ca8fa6 fix(macos): make Dock Quit cancelable
Route AppKit termination through the existing unsaved-document close transaction using the immutable winit patch. Restore pending prompts from hidden or minimized windows and preserve answers across repeated requests.

Validation: all six local gates, native cancellation/retry/save smoke, and hosted platform checks.

Fixes #544.

PR #623.
2026-10-12 14:52:12 -06:00
aiongg 698a87febc Integration: wgpu errors are logged only after the start-up frames, so a failing adapter still starts the app again on the next one (#612 x adapter restart) 2026-10-12 19:50:35 +00:00
aiongg b32deda339 Merge #612: Windows mixed-DPI drag keeps the window's size; GPU surface up to the adapter's texture limit
win-mixed-dpi 38dad0fb. Cargo.lock: next's lock plus the winit git source and the dpi 0.1.1 it brings (merged clean; cargo metadata --offline leaves it unchanged).

Conflict: apps/designcraft/src/main.rs: next's startup (adapter selection, catch_unwind, apple events) with log_uncaptured_errors at the top of its wgpu_render_state block; request_adapter_texture_size and mod gpu_limits merged clean
Conflict: ROADMAP.md: progress log both
Conflict: docs/hardware-parity.md: header keeps next's change list plus the mixed-DPI note
2026-10-12 19:49:59 +00:00
aiongg 527e5cbc0c Merge main: atomic desktop and CLI exports, staged book updates, recovery checkpoint retention, exit recovery
upstream/main c3859c82 (#614 e198eb5d, #615 9509b2bc, #616 8e151a2f, #617 c3859c82).

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
2026-10-12 19:46:34 +00:00
aiongg 38dad0fbde Windows: dragging across monitors with different scales doesn't grow or crash the window
winit 0.30.13 mishandles WM_DPICHANGED during a live drag on Windows 11:
the window flips between the two scales and grows past the GPU surface
limit. Pin winit to the one commit of rust-windowing/winit#4694, its
0.30.x backport of the fix.

The device asks for the adapter's largest texture side instead of
eframe's fixed 8192, and an uncaptured wgpu error is logged instead of
panicking, so a window larger than the surface limit draws wrongly
until it shrinks rather than closing the app.
2026-10-12 18:29:54 +00:00
skylar taylor-barrick c3859c828b fix(recovery): preserve documents on orderly app exit
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.
2026-10-12 12:18:32 -06:00
skylar taylor-barrick e198eb5d1d fix(export): replace desktop and CLI outputs atomically
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.
2026-10-12 12:04:39 -06:00
aiongg 3719470140 Integration: format 2026-10-12 17:26:21 +00:00
aiongg 3ac3bc90de Merge main (0.7.1): atomic saves, recovery per instance, quit keeps documents when cancelled
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
2026-10-12 17:14:07 +00:00
d020b4e85d fix(ui): preserve documents when quitting is cancelled
Prompt for unsaved documents on window close and application/menu quit. Defer discarding documents until all approvals are current, retaining every tab when a later decision cancels quitting.

Refs #544. Cancelable macOS Dock Quit remains separate.

Co-authored-by: aiongg <aiongtaigi@gmail.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-12 09:31:24 -06:00
Noah Scholzandskylar taylor-barrick 328b4d5a52 fix(recovery): isolate recovery data between app instances
Keep each app's recovery data in an owned run folder. Validate current folder and lock ownership and disable recovery when isolation fails instead of using the shared root.

Fixes #496.

Co-authored-by: noah1400 <48360809+noah1400@users.noreply.github.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-12 09:26:34 -06:00
aiongg 6d2163d497 Merge #580 (update): portable mode needs a writable DesignCraftData
Conflict: apps/designcraft/src/main.rs: next's catch_unwind/run_native app creator kept; the portable note is set after load_prefs inside it
2026-10-12 14:41:07 +00:00
Marco Arnulfo 1207ab14d3 feat(desktop): portable mode needs a writable DesignCraftData (#574)
A portable copy unzipped into a read-only folder such as Program Files
kept pointing settings and recovery at a DesignCraftData it could not
write, so both were lost without a word. The data folder is now probed
(created, then a file written and removed) and, when that fails, the
platform folders are used as if there were no portable.txt; the log and
the status bar say which folder could not be written.
DESIGNCRAFT_CONFIG_DIR still wins. Adds
a_portable_install_that_cannot_write_its_data_uses_the_platform_paths.
2026-10-12 16:30:58 +02:00
aiongg 1f90703104 Integration: format
rustfmt on the merged recovery_dir (#580 root_with with next's own_run).
2026-10-12 14:17:48 +00:00
aiongg 814153ba62 Merge #580: feat(desktop): portable mode, portable.txt next to the executable (#574)
Conflict: apps/designcraft/src/profile.rs: recovery_dir takes the PR's root_with (config override, else portable folder) and keeps next's .map(own_run) and own_run
Conflict: README.md: the PR's Portable mode section, then next's Graphics backend and processor section
2026-10-12 14:17:27 +00:00
Marco Arnulfo b4eedc42eb feat(desktop): portable mode looks next to DesignCraft.app on macOS (#574)
Inside a macOS bundle the executable sits in DesignCraft.app/Contents/MacOS,
a signed folder nobody edits, so the marker and DesignCraftData are looked
for next to the .app, as craft-settings does. Adds
inside_a_macos_bundle_the_marker_sits_next_to_the_app.
2026-10-12 15:09:02 +02:00
Marco Arnulfo 4acd5ba43c feat(desktop): portable mode, portable.txt next to the executable (#574)
A file named portable.txt beside the executable keeps settings, logs,
the GPU start-up record and crash recovery in a DesignCraftData folder
next to it instead of the per-user folders, as PhotoCraft and FilmCraft
do. A folder of that name is not a marker. DESIGNCRAFT_CONFIG_DIR still
wins, empty or not. The Windows portable zip now ships portable.txt;
the MSI and the macOS app bundle don't, so nothing changes there.
2026-10-12 13:45:05 +02:00
aiongg af389f9a0b Merge clipping-paths: Images: clipping paths from IDML clip the image on screen and in PDF
Conflict: ROADMAP.md: both progress lines
Conflict: docs/gaps.md: next's header, clipping paths added to its change list
Conflict: docs/target-app-parity.md: next's header, clipping paths added to its change list
Conflict: crates/doc/src/item.rs: both Graphic methods (next's shown_bounds, the branch's clip_region)
Conflict: crates/idml/src/names.rs: both imports (ClippingKind, ContourType)
Conflict: crates/idml/src/export.rs: both pushes, TextWrapPreference first, then the clipping settings
Conflict: crates/idml/src/import.rs: both lets (next's wrap, the branch's clip)
Conflict: crates/engine/src/cmd/file.rs: next's size and transform, plus clip: None
Conflict: crates/engine/src/cmd/datamerge/fill.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/engine/src/cmd/mathexpr.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/pdf/src/tests.rs: both tests
2026-10-12 05:37:19 +00:00
aiongg e111bb74f7 Images: declare a graphic's clipping path beside its transform 2026-10-12 05:32:11 +00:00
aiongg 8125f2490b Images: clipping paths from IDML clip the image on screen and in PDF
A placed graphic keeps its clipping path (Detect Edges, Alpha Channel,
Photoshop Path or User-Modified Path) in graphic space, with the
options IDML carries. IDML import reads ClippingPathSettings and its
saved ClippingPathGeometry (anchors capped, non-finite subpaths
dropped); export writes them back. The canvas and PDF export clip the
image to the path inside the frame clip; an inverted path shows the
image outside it. Without saved geometry nothing changes.

object.clippingPathOptions reports a graphic's clipping path, inverts
it or removes it (Object > Clipping Path > None).

Fixes #450.
2026-10-12 05:31:03 +00:00
aiongg c0b28a4478 Merge quit-unsaved: quitting asks to save documents with unsaved changes (#544)
Conflict: apps/designcraft/src/native_menu.rs: next's MAC_APP_MENU loop without the Quit arm; menus.rs MAC_APP_MENU ends with a dc-quit Cmd row running app.quit (AppMenuItem::Quit removed)
Conflict: crates/ui-egui/src/lib.rs: DesignApp fields, initialisers and tests keep both sides
2026-10-12 00:38:50 +00:00
aiongg 6aada5b74a UI: quitting asks to save documents with unsaved changes (#544)
Closing the window or Quit (Cmd+Q, now a menu command instead of the predefined
terminate item) asks about each unsaved document: Save, Don't Save or Cancel.
The control channel's app.quit still quits without asking.
2026-10-12 00:37:32 +00:00
aiongg ee2f4dffd6 Merge #506: Recovery: each running app keeps its own recovery data 2026-10-11 16:36:07 +00:00
noah1400 257fb08dbd Recovery: each running app keeps its own recovery data (#496)
Document uids start at 1 in every process, and every running app wrote
its recovery entries into the one recovery folder. Two apps wrote over
each other's `1.designcraft`, and an app that started reopened and
removed the entries of one still running.

A run now has a folder of its own in the recovery folder and holds a
lock in it for as long as it runs. An app that starts moves the entries
of runs that have ended, and those an earlier version wrote, into its
folder and reopens them. A locked folder is left alone.
2026-10-11 16:55:39 +02:00
aiongg 10d32f0b01 Integration: Interface Language lives in Preferences on next's Russian and macOS menu bar
prefs-language removes the Edit › Interface Language submenu; next added Russian and rebuilds
the macOS menu bar when the language changes (#397).
- de.rs and ru.rs drop their "Edit/Interface Language" path entries, as ja.rs does.
- The Russian language test checks the Preferences list instead of the removed submenu.
- app.language's params doc lists ru and says the macOS menu bar follows at once.
- native_menu.rs: the rebuild comment names Preferences and Cancel, which change
  `app.ui.language` and so reach the same rebuild in `NativeMenu::poll`.
2026-10-11 14:13:23 +00:00
aiongg bbf7ea975f Merge mac-titlebar update: macOS: dragging a title-bar control no longer moves the window
Conflict: apps/designcraft/Cargo.toml: one objc2/objc2-app-kit entry with next's NSTextInputContext and the branch's NSApplication/NSResponder/NSWindow features; next's fmv-macos-events kept
Conflict: apps/designcraft/src/main.rs: next's App fields, GPU startup, IME discard and Apple events kept; the branch's `movable` field and `mac_window::sync_movable` call added before `startup.leave()`
2026-10-11 14:10:18 +00:00
aiongg 44f3f304a5 macOS: dragging a title-bar control no longer moves the window
AppKit moves the window when a drag starts anywhere in the title-bar band
of a full-size content view, on the application bar's controls too. The
title bar now reports whether the pointer is on one of its controls
(title_bar::window_movable), and the macOS app sets the window's movable
flag from it each frame it changes, through objc2-app-kit's safe methods
(the versions muda already uses). Home, the menus, search, the workspace
menu, Share and Discord keep their drags; empty bar space still moves the
window, and so does the band while a modal covers the bar.
2026-10-11 13:40:28 +00:00
aiongg fc40ffed2c Merge #475: CLI: --export writes the file type its extension names 2026-10-11 12:23:53 +00:00
noah1400andClaude Opus 5.5 40e8dbc6fb CLI: --pdf-options that is no JSON object is an error
`run --pdf-options '[1]' --export a.pdf` panicked when the PDF's path
was added to the options (exit status 101). The value is now checked
where it is read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:55:20 +02:00
noah1400andClaude Opus 5.5 8547a9fca4 CLI: --export writes the file type its extension names (#432)
--export knew .pdf, .epub, .idml and .designcraft and saved a PNG of
the page under every other name: out.html, out.txt, OUT.PDF, and
out.designcraft from `script`. The exit status was 0.

.html and .htm now run file.exportHtml, .rtf and .txt run
file.exportText (the story being edited or of the selected frame), and
extensions match in any case. A name with another extension is an
error before anything runs; one with no extension is still a PNG.
`run` and `script` share one list, and `script` saves a copy for
.designcraft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:55:05 +02:00
aiongg 2fae23fcc6 Merge atomic-save: MCP: exported files replace the target atomically (#436)
Conflict: ROADMAP.md: both progress-log entries, next's newer ones first
Conflict: apps/designcraft/src/gpu.rs: next's side; the branch's test fix targets the startup record tests next replaced
Conflict: apps/designcraft/src/main.rs: next's gpu startup and macOS IME helper; config_dir() dropped for the branch's profile::config_dir(), recovery_dir from profile::recovery_dir()
Conflict: crates/ui-egui/src/canvas.rs: next's guard (menu_open, keyboard_pane already limits keys to the focused pane)
Conflict: crates/ui-egui/src/lib.rs: modules from both sides
2026-10-11 05:16:10 +00:00
aiongg 7935c60617 Merge mac-app-menu: macOS: Settings… is in the app menu
No conflicts. native_menu.rs merged automatically: install() builds the
app menu from MAC_APP_MENU and the bar from native_menu_tree(), and
next's poll() rebuilds through install() when the language or Show CJK
Features changes, so a rebuilt menu has the same app menu and keeps
dc-settings in its items map.
2026-10-11 02:53:33 +00:00
aiongg 60f2c597a5 macOS: Settings… is in the app menu
The macOS menu bar shows Settings… (app.preferences, Cmd+K) in the application menu after About and leaves it out of Edit. The app menu labels go through the interface language. Windows, Linux and the web keep Edit › Preferences….
2026-10-11 02:34:49 +00:00
skylar taylor-barrick 2bdb0ea01d UI.9: recover saved docking metadata and declare frontend MSRV 2026-10-10 18:52:20 -06:00
aiongg 0c1d021dfd Integration: macOS menu bar rebuilds when Show CJK Features changes (#397, #336)
NativeMenu keys its rebuild on (language, cjk_features(app)), so the
CJK commands that menus::shown filters appear or disappear at once.
2026-10-10 23:24:57 +00:00
aiongg 3667fcc3a3 Merge #397: macOS menu bar follows the interface language at once
No conflicts.
2026-10-10 23:24:36 +00:00
aiongg 62df5e764a Merge file-open-os: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- apps/designcraft/Cargo.toml: the macOS target keeps objc2 0.6 / objc2-app-kit 0.3 (IME) and
  adds fmv-macos-events =0.1.0, which uses the same objc2 and objc2-app-kit versions.
- apps/designcraft/src/main.rs: the shared Arc<gpu::Startup>, catch_unwind and restart flow
  stay; the Apple-event handlers are installed before run_native (outside catch_unwind, held
  until the window ends), the app drains the inbox each frame, and documents that arrived are
  noted on the Startup. The open dialog keeps the INDD/INDT filter and ends with All files.
  The open-filter test checks both; the save-dialog test is added.
- apps/designcraft/src/gpu.rs: Startup::opening records the documents the OS handed this run;
  a start again after a graphics failure (both finish and the first-frame watch) passes them
  as file arguments, since Finder doesn't send them twice.
- apps/designcraft/src/apple_events.rs: poll returns the documents it handed the app, and
  queued() returns those still in the inbox when the window ends.
- crates/engine/src/cmd/file.rs: file.open decides by content first (IDML by its mimetype
  entry, then a DesignCraft package or JSON), then an InDesign file by its signature or
  .indd/.indt extension, then a .idml or .designcraft extension for the reader's own error;
  anything else is "not a DesignCraft or IDML file". INDD stays in file.openBytes. Save
  commands add .designcraft as on file-open-os; the params docs describe all of it.
- crates/engine/src/cmd/mod.rs: re-exports from both sides (is_document,
  with_document_extension and layout::chapter_numbering).
2026-10-10 23:21:12 +00:00
con3codeandClaude Opus 5.5 cc12ea576f macOS: the menu bar follows the interface language at once
The system menu bar was built once at launch, so Edit › Interface
Language changed the panels but left the menus in the old language
until the next launch. It is rebuilt when the language changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 07:49:21 +09:00
aiongg 11532d961e Documents: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- file.save / file.saveAs / file.saveACopy add the .designcraft extension to a
  given path that lacks it and report the path written; saving back to the
  document's own file keeps its name.
- file.open tells DesignCraft and IDML packages apart by content, so files
  without an extension open; anything else is "not a DesignCraft or IDML file".
- Dropped or web-picked files without an extension open as documents when their
  content is one.
- Desktop save dialogs filter on the suggested extension and add it to a name
  typed without it, asking before replacing an existing file; the open dialog
  gains "All files".
- macOS: open-documents Apple events (Finder, Dock, Open With) through
  fmv-macos-events, queued until the window is ready; Info.plist declares the
  .designcraft type and an imported .idml type with CFBundleDocumentTypes.
- Windows MSI: .designcraft ProgId with an Open verb, IDML under Open with.
- Linux: shared-mime-info for both types and MimeType in the .desktop file.

Fixes #158, #211, #375
2026-10-10 22:40:06 +00:00
aiongg fbfd0b2fe9 Merge gpu-startup: recovered documents survive restarts, the watchdog only restarts a hung adapter, software adapters last, the skip list as an argument
Conflicts: designcraft main.rs ui() (#366's macOS marked-text discard, then the startup watch's leave);
engine recovery.rs (the branch's rewrite-then-discard with next's bump_revision).
2026-10-10 21:28:17 +00:00
aiongg 158f366af8 Startup: restarts keep recovered documents, a busy UI never counts as a GPU hang, software renderers last
- Recovery: file.recovery.open moves each entry to the reopened document's uid
  (written before the old one is removed), so recovered documents stay on disk
  until saved or closed, also when the app restarts on another adapter or
  stops before its first recovery save.
- First-frame watch: a late first frame is logged and noted in the status bar.
  The adapter counts as hanging only when the UI thread has been stuck outside
  the app's code (logic/ui mark entry and exit) with no progress for 60 s while
  the window isn't known to be minimized, occluded or unfocused. The watch asks
  for a repaint on each look, so an idle UI ends it. VectorCraft restarts on
  the late frame alone, which a held-back compositor or a long compose can set
  off.
- Software renderers (WARP, llvmpipe) are passed over while another backend is
  left; once no hardware adapter of any backend could show the window, the app
  starts again with them allowed, once.
- A restart clears gpu.json's backend entry just before exec or spawn and puts
  it back when starting again fails. Backends a restart left out are no longer
  recorded as failed.
- The watch ends a hung process with TerminateProcess on Windows.
- The skip list travels as the restarted app's --gpu-skip= argument instead of
  an environment variable, so processes the app starts don't inherit it.
2026-10-10 21:26:51 +00:00
aiongg 9d8bdaef2c Merge wrap-placed-graphic: the CLI's performance document 2026-10-10 21:26:25 +00:00
aiongg 70a04944a5 Layout: the CLI's performance document builds its graphics with a wrap 2026-10-10 21:26:25 +00:00
skylar taylor-barrick c62866443c UI.9: integrate shared panel docking and preserve workspace behavior 2026-10-10 14:42:40 -06:00