The drag strip beside the tabs, the column bar, the floating title bar and the strip above an icon group stay draggable, with the grab cursor and the hover outline, but paint no grip lines.
DialogState::show_error shows an app's message under the table until the
user dismisses it (a new Dismiss button on every message) or the next
action replaces it. ForeignSet::truncated carries the entries the app's
reader dropped; plan counts them in ImportReport::truncated, which the
import review shows.
Bindable::import_paths (with_import_path) are read by import matching
like menu paths, and make no areas or groups in the dialog, so a tool's
["Tools", label] path no longer shows a Tools Menu entry.
The dialog's conflict count, Conflicts filter and row warnings leave out
pairs whose commands both have the chord among their defaults, as
Keymap::clashing does.
Outcome, ConflictMode, ProfileError and Options are non_exhaustive, with Options::with for
options built in one expression. Profiles keeps its fields private behind active() and sets(),
lists the sets loading dropped in problems(), and stores a format version (a store without one
reads as version 1).
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The renderer looked up each panel's place with a tree walk, found
recorded content heights by linear search and named every possible
control of every panel to record hit rects. Places come from one
traversal per frame, content heights through an index, and hit rects
are named only when hit_rects asks.
OpenAt checked the opened panel's movable, so a locked workspace refused
OpenAt while Open worked. It checks movable only for a panel already
shown. Workspace::set_visible takes the app's permissions, so hiding
cannot close a protected canvas.
On a dock at the right edge the column handle passed accessibility
values through the physical axis, so SetValue 300 gave width 180 and
Increment shrank the column. ResizeHandle::reversed applies
accessibility actions in value space; the pointer and arrow keys stay
physical.
Keeping focus on the handle after a pointer drag broke app shortcuts
that defer to a focused widget (egui_wants_keyboard_input), and arrow
keys moved both the canvas selection and the divider. A handle focused
by the pointer gives the focus up on release; one reached with Tab or
F6 keeps it, and arrow keys resize it.
Restoring beside a sibling subtree needed the exact same set of panels,
so a migrated layout without the canvas put the canvas in hidden. Restore
splits the anchor alone when the subtree changed. reconcile takes the
panels' permissions and never leaves a registered panel that cannot
close in hidden: it goes back to its recorded or default place, else to
the outer edge of the docked area.
Switching sets closes an open prompt, deleting a set reloads only when it was the active one, and
deleting the last set with no built-ins loads no overrides.
On the first panel of the last column, the command picked that panel as
its own anchor and failed with InvalidPlacement. The anchor is a panel of
the last column outside the moving unit; when the unit already is that
column, the command does nothing.
A layout saved on a larger screen drew its outer columns 0 wide with no
hit rect and resized them to width 0, which the model rejects. Columns
shrink in proportion down to their minimum width; resizing one fixes the
others at their drawn widths so the divider follows the pointer.
* print, ui: a printer is shown by its name, not its CUPS queue name
CUPS queue names can't hold dots or spaces, so macOS adds the printer at
192.0.2.15 as the queue `_192_0_2_15`, and the Print dialog showed that
underscored name. The name the user knows is the queue's description
(CUPS `printer-info`).
craft-print now lists queues with `lpstat -l -p -d` (same lines, plus
each queue's indented details) and keeps the first `Description:` as
`Printer::description`, capped at 256 characters. Windows names are
already human, so they leave it empty.
craft-ui's `Printer` gains `label`; the dialog shows it, or the name when
it's empty, and `shown_names` adds the queue name in brackets when two
printers would show the same text. Selection and jobs still use `name`.
Both structs gain a field, so apps that build them by hand add it
(`..Default::default()` works for craft-ui's) when they update the pin.
* print, ui: an empty first description stays first, and shown names never collide
parse_lpstat tracks whether a queue has had its Description line, so an
empty first one isn't replaced by a later one. shown_names checks the
final texts: printers whose bracketed text another label already reads
show their queue names instead.
Hiding panels and dropping the tab strip tested for exactly
Permissions::PROTECTED, so a canvas set up as the docs describe
(accepts_splits on) was hidden and got a tab strip. Permissions::is_protected
decides from what the panel allows.
* media: add craft-media with bitstream and isobmff from FilmCraft
Moves filmcraft-bitstream and filmcraft-isobmff (storytold/filmcraft@6c3948a)
into one crate with a feature per module. The public API matches FilmCraft's so
its crates can become re-exports. Tests, oracle tests and fuzzing come along;
the ffmpeg discovery helpers replace filmcraft-testkit.
Out-of-contract inputs no longer panic: bit widths above 32, a zero NAL length
prefix width, and PCM frame sizes that overflow u32. Adds
BitReader::read_ue_plus from DeckCraft's copy.
* media: public ISO BMFF box reader (isobmff::boxes)
Make the demuxer's box iterator and big-endian reader public as
isobmff::boxes::{BoxIter, BoxRef, BoxHeader, Reader}, for walkers of
boxes the demuxer does not model (CR3 uuid boxes, HEIF meta items,
JPEG 2000). BoxIter::within keeps offsets relative to the outer slice,
and allow_truncated yields a cut last box. The fuzz test walks every
box of each mutated file.
* media: matroska feature from FilmCraft's filmcraft-matroska
Port storytold/filmcraft@6c3948a crates/matroska as craft_media::matroska
behind the opt-in feature matroska, with the same public API so FilmCraft
can make filmcraft-matroska a re-export. Its unit and ffprobe oracle tests
come with it, on craft-media's test helpers.
From DeckCraft's copy: cluster and block timestamps no longer overflow;
they saturate at i64::MAX. MuxingApp and the default WritingApp name
craft-media. New tests: block additions and mutation fuzzing. The oracle
skips the duration of a track's last packet when ffmpeg 8 shortened it.
* media: MOV PCM uses lpcm v2 above 16.16 rates and for unsigned 16-bit; signed 8-bit as twos
Version 0 sound descriptions store the rate as 16.16 fixed point, so 88.2 kHz
and higher were written as 0 Hz. 8-bit signed PCM was labelled raw (unsigned)
and 16-bit unsigned as twos/sowt (signed). The reader also took raw under a
sound handler for uncompressed video.
* media: stop the top-level box scan at a box that runs past the end of the file
A 64-bit box size near u64::MAX saturated the scan offset, and the next
offset + 8 overflowed.
* media: one sample budget for the whole ISO BMFF file
The sample cap and the file-length bound on samples without table entries
(constant-size stsz, trun without per-sample fields) applied per track or
per trun, so repeating a small trak or trun multiplied the allocation.
Both are now shared by all tracks and fragments.
* media: set_sample_entry rejects entries that change the track's media type or PCM frame size
The handler, stsz constant size and PCM timescale check are fixed at add_track;
a replacement that changed them produced a corrupt file. A bad track index
returns NoSuchTrack like the other writer methods.
* media: saturate the sample end in MkvFile::duration_ns
Cluster timestamps saturate at i64::MAX, so pts + duration could overflow
for files without Info/Duration.
* media: checked add for the Matroska cue cluster position
CueClusterPosition comes from the file; an add past u64::MAX panicked in
debug builds and wrapped in release. Such a cue now falls back to the index.
* media: cap the Matroska TimestampScale at 2^32 and saturate tick conversions
A scale near 2^64 overflowed u128/i128 in the per-lace split and in
Track::ticks_to_ns. A BlockDuration near u64::MAX also overflowed the tick
difference at the default scale. Larger scales are ignored like a zero scale,
and tick and nanosecond values outside i64 saturate.
* media: Matroska fixture generation no longer truncates the shared subtitle inputs
Parallel tests generating subs.mkv or webvtt.webm could read in.srt or in.vtt
while another thread rewrote it, and cache a fixture with missing packets.
* media: rustfmt parse_moof signature
* ci: add craft-media workflow covering feature sets, WASM and ffmpeg oracles
The lint job runs fmt, clippy for each feature set (none, bitstream,
isobmff, matroska, all) and a wasm32 check. The test job runs the
all-features tests with Ubuntu's ffmpeg and CRAFT_MEDIA_REQUIRE_ORACLES=1,
installing ffmpeg while the tests compile.
* media: Matroska lacing keeps timestamp gaps
A laced frame's timestamp is implied by the block's and the track's
DefaultDuration, so lacing collapsed gaps in the input. A frame now joins
a lace only when that implied tick equals its own, and tracks without a
DefaultDuration do not lace. The laced remux oracle compares timestamps.
* media: required oracles fail on fixture generator and ffprobe failures
Every path that gave up on an oracle check (missing tool, failed fixture
generator, failed ffprobe run) goes through one helper that prints
SKIPPED, or fails the test with CRAFT_MEDIA_REQUIRE_ORACLES=1. Fixture
generators of both containers share run_generator, which takes the
required flag explicitly so tests can exercise it without changing the
process environment.
* media: use US spelling
* media: a failed ffprobe probe skips the optional oracle check
* media: the Matroska oracle compares the last subtitle cue's duration
Ink managers need the end of a spot's alias chain even when it prints as
process color, which plate_of reports as None. composite::appearance
gives apps the same spot swatch ramp the composite uses.
print::cmyk_blending(profile) blends transparency in CMYK and writes no
output intent or /Trapped, as a non-PDF/X print PDF does. print::settings
builds on it and adds the GTS_PDFX output intent and /Trapped /False.
CodeRabbit (storytold/wordcraft#3252): reload_dictionary() wrote DICT and DICT_IN_MEMORY
under two separate locks. If install_dictionary() ran between them,
the final state could hold an in-memory dictionary with the flag left
false, so installed_path() would report a same-named disk file
instead of None.
Combined both into one DictState struct behind a single RwLock in
both ru and cs, so install_dictionary() and reload_dictionary() each
update the dictionary and its origin in one critical section.
Didn't add a dedicated concurrency test: genuinely proving a race's
absence needs real multi-threaded stress testing, and the previous
commit on this PR already found that tests directly exercising
install_dictionary()/reload_dictionary() concurrency in this crate's
shared global state cause real, reproducible flakiness in unrelated
tests. The fix is correct by construction (one lock acquisition around
both field writes) and all 222 existing proof tests, run 15 times in
a row, stay green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CodeRabbit (storytold/wordcraft#3252): installed_path() searched the filesystem
unconditionally, so a dictionary given directly with install_dictionary
(a user-picked one, or a test fixture) was reported as the file of a
same-named dictionary that happened to also be installed, even though
spelling was actually using the in-memory one, not that file.
Added a DICT_IN_MEMORY flag to both modules, set in install_dictionary
and cleared in reload_dictionary; installed_path() now returns None
immediately when it's set, before even searching the filesystem.
Tried adding a dedicated test that calls install_dictionary() and
asserts installed_path() is None. It made dictionaries_folder_is_
searched_first flaky (~15-35% of runs across repeated local testing):
ru's and cs's own test modules already call install_dictionary()
directly in several tests of their own, sharing the same process-wide
static, and dictionaries_folder_is_searched_first's exact-path
assertion for Russian/Czech could observe whichever one last touched
it. Reverted the dedicated test; the production fix alone never
touches that shared state (it's a pure read-only check) and caused no
flakiness by itself across 25 repeated runs. Instead, narrowed
dictionaries_folder_is_searched_first's Russian/Czech assertion to the
language only, not the exact path, since unlike the other four
languages, installed_path() for these two is now sensitive to
concurrently-running tests' legitimate in-memory installs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CodeRabbit (storytold/wordcraft#3252): set_dictionaries_folder() reloaded every other
proofed language but not Russian or Czech, so after a folder change
installed_dictionaries() would report a path from the new folder
while spelling kept using the stale cached dictionary.
Also added Czech's test fixture to the shared dictionaries-folder
test's temp folder (the same content czech_proofing_through_the_
review_commands installs directly): since that folder-change test
now reloads Czech too, it was clearing a concurrently running Czech
test's installed dictionary with nothing to replace it, intermittently
emptying it mid-test. Same fixture content either way keeps the race
harmless, exactly as it already does for Polish/Portuguese/Indonesian.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
installed_dictionaries() never included Russian or Czech at all,
because neither crates/proof/src/ru nor crates/proof/src/cs had an
installed_path() function for it to call (every other proofed
Hunspell language — Polish, Portuguese, Italian, Croatian, Turkish,
Indonesian — already did). File > Options > Proofing's dictionaries
list iterates installed_dictionaries() directly, so Russian and Czech
never showed up there at all, installed or not, matching the report.
Added installed_path() to both modules, mirroring the existing
pattern (crate::hunspell::find(NAMES).map(|(_, dic)| dic)), and added
both to installed_dictionaries().
The actual spell-checking dispatch (is_correct_in_with) already
routed Russian and Czech correctly — confirmed by installing the real
system ru_RU Hunspell dictionary and checking it directly
(ru::is_correct("молоко") true, ru::is_correct("малоко") false), so
that part of the pipeline isn't what's broken. The reporter's other
symptom (Russian text in the document not being checked at all) did
not reproduce with the real dictionary installed; I could not pin
that part down further without more specific repro details from
their setup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Patch 06 records that DeviceN fails export under PDF/A-1 to -3 (its tint transform is
PostScript) and is written under PDF/A-4; patch 08 records the second output intent and
the caller's CMYK destination profile.