Commit Graph
1933 Commits
Author SHA1 Message Date
Martin BallandClaude Opus 5.5 f7cc8a7df1 i18n: row-problem log and README name precedence (review)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 22:11:30 +01:00
Dylan M. Taylor 1d041f5c03 Merge pull request #249 from dylanmtaylor/ci/sonar-main-not-cancelled
ci(sonar): stop cancelling main-branch runs so coverage is published
2026-10-23 16:59:06 -04:00
Dylan M. Taylor 562aa90af5 ci(sonar): don't cancel superseded runs on main, so coverage is published 2026-10-23 16:51:10 -04:00
aiongg f0ad6c1e1d media: QuickTime v2 sound entries refuse channel counts and lpcm bit depths past 16 bits (#246)
From DeckCraft 6873a25.
2026-10-23 16:14:41 -04:00
Martin BallandClaude Opus 5.5 4ecda6fa8c i18n: parse each locale lazily on first lookup
I18n::new keeps the embedded text and parses nothing; a locale's rows
are parsed on the first lookup that reaches it (also as a fallback) and
its bad rows logged with log::warn!. new returns only the locale-level
problems. check_all parses every locale and returns all problems.
name() scans for the language.name row instead of parsing the locale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 21:11:48 +01:00
aiongg f97c84d273 media: fixes from FilmCraft's copy (NAL widths, RBSP stop bits) (#245)
* media: reject NAL length prefix widths outside 1 to 4

length_prefixed_nals returns BitError::InvalidNalLengthSize for any other
width before scanning, empty data included. A width of 5 or more was
accepted and shifted bytes out of the length; a zero width returned Eof
only for non-empty data. Width 3 still splits, since
(lengthSizeMinusOne & 3) + 1 can be 3.

From filmcraft#2953.

* media: find the RBSP stop bit once per BitReader

BitReader::new records the position of the last set bit, and
more_rbsp_data compares against it instead of scanning back over the
data's zero suffix on every call (once per macroblock in a slice
parser). FilmCraft caches it in a Cell on first use; computing it in
new keeps BitReader Sync.

From filmcraft#2880.

* media: test the order CodecPrivate stripped headers are restored in

Header-stripping encodings with private-data scope prepend their bytes in
list order, so the last one listed ends up first. Empty prefixes and
frame-only scopes leave CodecPrivate as it is.

From filmcraft#2911.
2026-10-23 15:52:41 -04:00
aiongg 7474826f6f media: craft-media review fixes (frame budget, PCM chunks, spec names) (#196)
* media: look up the track of each traf in a hash map

* media: bound Matroska indexing by a file-wide frame budget

Every indexed frame claims its stored bytes and at least one, so fixed-size
laces of empty frames cannot describe more frames than the file has bytes.
The frame count across tracks is capped at 2^26, the TrackEntry count at
2^16, and blocks find their track through a hash map.

* media: keep the spec spellings of Colour, ColourSpace and colourPrimaries

* media: PCM chunks stay bounded in MOV and MP4

The writer ends a PCM chunk at 1 MiB or half a second, and returns an
error past 2^32 - 1 samples in a track instead of clamping the stsz count.
The reader splits a PCM chunk above 16 MiB into several samples, so files
from writers without chunk limits read whole past 4 GiB. A sparse output
lets the tests write past 4 GiB, which also exercises the writer's co64
path.

* media: clamp max_lace_frames to 256 and round BlockDuration

* media: level-1 Matroska elements read at most the file's length in all

SeekHead entries pointing at nested elements made each one read the rest
of the file.

* media: set_sample_entry rejects a codec change

* media: the mux oracle compares stream counts before streams

* ci: media checkout does not persist credentials

* media: README lists the renames from FilmCraft and the output that differs on valid input

* media: rustfmt

* media: the stream count comparison leaves out timecode streams

* media: PCM chunks end at 1 MiB only, as ffmpeg's do

ffmpeg's PCM chunks run past half a second, so a duration limit split
remuxed chunks the source kept whole.

* media: frames and samples indexed scale with the file's length

Matroska indexes at most one frame per 8 bytes of file and ISO BMFF at most
one sample per 4 bytes, beyond the first 2^20 of each, so the sample tables
stay within about ten times the file's size. Empty fixed-size laces and
one-byte constant-size samples could make them 40 to 76 times its size.

* media: clamp the frame and sample allowances
2026-10-23 15:42:51 -04:00
aiongg 46a49971c7 docking: format 2026-10-23 18:06:20 +00:00
aiongg 909410258b docking: document native window placement, moves and mixed scale factors 2026-10-23 18:06:10 +00:00
aiongg e2b452482a docking: an example with native floating windows 2026-10-23 18:05:09 +00:00
aiongg 37b6a0dc25 docking: native windows move only on a pointer move of their frame
- A bar drag reads this frame's pointer moves only. egui keeps the last
  pointer position, measured against where the window was then; reused
  after the window moved, it crept the window on.
- Escape pressed in any of the application's windows cancels a drag a
  native window owns.
- DockContent::allow_native defaults to true: the feature is the opt-in.
- DockOutput::forwarded_modifiers tells Cmd/Ctrl+C, X and V, which
  egui-winit sends as Copy, Cut and Paste, from those keys.
2026-10-23 18:05:09 +00:00
aiongg 98e05997a0 docking: native windows place by their content and follow the layout
- A window made this session opens at its computed rect; only windows of a
  loaded layout are restored onto a connected monitor.
- The content (inner rect) is each native window's desktop origin; only
  OuterPosition converts to the outer position.
- A bar drag places the window from its reported position, so late moves
  do not overshoot. The layout follows once the drag ends, and a window
  left with its bar off every monitor goes back onto the nearest.
- A changed Floating::native (undo, reset, reload) moves the OS window
  instead of being overwritten by its next report.
- Dragging a native window's only tab or group docks it whatever
  title_bar_docks says.
- Escape that cancels a drag is consumed; nothing is forwarded while a
  widget of the window has focus.
- A bar drag whose window goes away ends.
- An embedded fallback keeps its drop targets; floating windows get no
  taskbar entry.
- The test host can give windows a frame inset and land moves a frame late.
2026-10-23 17:55:03 +00:00
aiongg 0ac8f80f64 docking: a loaded layout's monitor and monitor size are validated 2026-10-23 17:55:03 +00:00
aiongg e19b1b684d docking: a restored native window opens where its bar can be grabbed 2026-10-23 17:48:12 +00:00
aiongg 137845d6f4 docking: clamping a native window onto a fractional monitor origin cannot panic 2026-10-23 17:42:37 +00:00
aiongg 783793cbbc docking: document where a native window's bar docks it 2026-10-23 17:31:11 +00:00
aiongg b46b9dd5c1 docking: a native window's bar docks it at the main window's frame edges
The window docks as its groups at a frame's edge zones, as a group drag would; a body's
center still takes no whole window. Tests for Escape during a bar drag and for the
window focused last being in front.
2026-10-23 17:31:11 +00:00
aiongg 619c93ea29 docking: document drags between OS windows 2026-10-23 17:24:25 +00:00
aiongg 84b42eacc9 docking: tabs, groups and windows drag between OS windows
The window where a drag starts owns it and writes its pointer with its desktop origin.
The window under that point shows the preview with its own targets, and the release
docks there, or floats a new window over the desktop. Leaving a window is no release
with native viewports. Dragging the only tab or group of a window moves it, and its
bar docks the whole window.
2026-10-23 17:24:25 +00:00
aiongg 141ddc92f6 docking: which window lies under a desktop point 2026-10-23 17:24:19 +00:00
aiongg 48272d3932 docking: the host stand-in needs no expect, and the label count drops with New Window 2026-10-23 16:42:36 +00:00
aiongg 0ecc7d2d39 Merge remote-tracking branch 'ao/docking' into dock-windows 2026-10-23 16:42:21 +00:00
aiongg e350bfdb01 docking: the undock hint test uses contains, as clippy asks 2026-10-23 16:40:17 +00:00
aiongg e23e447f32 Merge remote-tracking branch 'ao/docking' into dock-windows 2026-10-23 16:35:58 +00:00
aiongg 53947dbfe8 docking: the undock hint test expects the platform's key 2026-10-23 16:35:35 +00:00
Federico Olivares 834044eb29 Merge origin/main into spanish-proofing
Resolve the ROADMAP.md conflict: the status line keeps both changes, and main's
revision-history row comes before the Spanish proofing one.
2026-10-23 13:20:42 -03:00
NetterSilverandClaude Haiku 5.5 1711c147cb Cap and deduplicate the .aff TRY alphabet at parse time (storytold/wordcraft#3981)
Fixes storytold/wordcraft#3654

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-23 11:44:50 -04:00
Dylan M. Taylor f180fc0d35 Use chrono instead of hand-rolled UTC conversion (#219)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days and Day::to_days in
crates/ai-core/src/history/report.rs with chrono, added as a workspace
dependency (std-only, so ai-core still compiles for wasm). The day/month
grouping, ISO and CSV timestamps and the Day/Month display are byte-identical
and pinned by the existing tests. AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* history: preserve out-of-range conversion failure in civil_from_days

civil_from_days used `i32::try_from(..).unwrap_or(0)` before calling
NaiveDate::from_num_days_from_ce_opt, so a CE day count past i32 (e.g.
from Day::from_days(i64::MAX)) passed a valid day 0 to chrono and read
as 0000-12-31 instead of the 0000-01-01 fallback the None arm gives for
every other out-of-range date. Convert with `.ok().and_then(..)` so both
the integer-conversion and NaiveDate-construction failures share the one
fallback. Regression test covers i64::MAX and i64::MIN.

Doc comments on the chrono-touched to_days/from_days/civil_from_days now
state the out-of-range behaviour (0 / 0000-01-01) for the docstring gate.

* Convert every i32 year, not only chrono's range

Day holds any i32 year, but NaiveDate stops near ±262143, so years like
262144 read as 0 / 0000-01-01. The Gregorian calendar repeats every 400
years: chrono now converts the day within its cycle and whole cycles are
added back, in both directions. Values match the pre-chrono formula
(tested for 262144, -262145, i32::MAX and i32::MIN). Nonexistent days
still read as 0, and day counts whose year doesn't fit i32 still read as 0000-01-01.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:17:51 -04:00
Dylan M. Taylor 9def84d7a5 security: fallible bitmap allocation in vendored hayro-jbig2 (#242)
* security: fallible bitmap allocation in vendored hayro-jbig2 (follow-up to #239)

* security: reinitialize allocates before mutating the jbig2 bitmap (CodeRabbit)
2026-10-23 11:16:19 -04:00
Dylan M. Taylor 35d18945a9 fix(hayro-syntax): read full /W[0] xref entry type width (#241)
* fix(hayro-syntax): read full /W[0] width for xref stream entry type (follow-up to #239)

* hayro-syntax: loader-path test for oversized /W[0]; document patch; drop committed build output (CodeRabbit)
2026-10-23 11:14:16 -04:00
Federico Olivares f4fe03610d storytold/wordcraft#3360: user dictionary and Ignore All use the composed key spelling uses
Maintainer review on storytold/wordcraft#3562: spelling looks words up composed (NFC), but
add_word, remove_word, ignore_word, custom_word (so loading the dictionary
file) and save_user_dictionary's removed list kept the raw lower-cased
spelling. A word added or ignored decomposed (`zqprue\u{301}ba`) therefore
stayed flagged.

- dictionary_key(): lower case, then composed; every write uses it, and
  in_user_dictionary() looks up composed, so either form adds, ignores,
  removes and finds the same entry; NFD entries in an existing file load
  and save composed
- English's known_core() reuses in_user_dictionary() instead of a copy of it
- issue byte ranges still cover the text as typed
- regressions: proof (add, Ignore All, remove, save/load with an NFD entry,
  both forms rechecked) and engine (review.addToDictionary and
  review.ignoreAll with the issue's NFD text)
2026-10-23 10:31:35 -03:00
Dylan M. Taylor 6cefd467fc security: bound vendored hayro xref/object-stream/JBIG2/graphics-state work and krilla PNG size (CodeRabbit) (#239)
* security: bound vendored hayro xref, object-stream, JBIG2 and graphics-state work; krilla PNG size

hayro-syntax:
- xref-stream subsections must be backed by decoded bytes: zero /W widths and counts
  longer than the data are refused before inserting (CodeRabbit CWE-400, xref.rs:962)
- object-stream offset tables are parsed once per document and cached in Data
  (CodeRabbit CWE-407, xref.rs:1079)
- embedded JBIG2 pages over 2^28 pixels are refused before allocating, with checked,
  fallible reservation (CodeRabbit CWE-789, filter/jbig2.rs:34)
- a JBIG2Globals stream with an image filter is ignored, so globals cannot recurse
  (CodeRabbit CWE-674, filter/jbig2.rs:20)

hayro-interpret:
- at most 4096 saved graphics states / 64 MiB retained; dash arrays over 256 entries stroke
  solid (CodeRabbit CWE-400, context.rs:226)

krilla:
- PNGs implying more than 1 GiB of decoded output are refused at Image::from_png and in
  the deferred decoder; the buffer is reserved fallibly (CodeRabbit CWE-789,
  graphics/image.rs:565)

SOURCE.json local hashes updated; patches recorded in vendor/README.craft-reader.md and
vendor/krilla/README.craft.md.

* vendor: fix SOURCE.json local hashes; reserve krilla's colour/alpha split buffers fallibly

- SOURCE.json: only the local sha256 changes; the source/upstream hashes of the patched
  files are restored (CodeRabbit review)
- krilla handle_u8_image/handle_u16_image reserve their split buffers with try_reserve_exact
  and return an error, so a valid image under the 1 GiB decode cap cannot abort the process
  on the second allocation (CodeRabbit review)
2026-10-23 08:39:26 -04:00
Federico Olivares 970bfcf7a8 storytold/wordcraft#3360: Spanish proofing code clean-up (no behaviour change)
- es/mod.rs: one variety() match gives the dictionary slot, log name and file
  names (variety_name() folded in); load_installed() destructures it
- es/grammar.rs: the sentence-start rule for the missing ¿/¡ is one named
  function, ends_clause(), replacing is_boundary() and three closures
  rebuilt per mark; in_address() is shared; repeated_words() builds its
  helpers once instead of per word pair
- es/suggest.rs reuses the Czech ranking's MAX_LETTERS instead of a copy

Same tests pass unchanged; a temporary old-vs-new differential proptest
(20,000+ generated inputs with URLs and punctuation) found identical output.
2026-10-23 09:19:18 -03:00
Federico Olivares ecb4638980 storytold/wordcraft#3360: decomposed accents stay in their word; URLs aren't Spanish questions
Maintainer review on storytold/wordcraft#3562:
- words() keeps combining marks with the word they follow, so a word typed
  decomposed (NFD, `tambie\u{301}n`) is one word with its own byte range;
  is_correct_in_with and suggest_in look it up composed (NFC), so it is
  checked like `también` in every language (unicode-normalization, MIT/Apache)
- the Spanish missing-¿/¡ rule skips marks in web and e-mail addresses
  (`Ver https://example.com/?`), and punctuation inside an address or a word
  (`example.com`, `https:`, `10:30`) no longer starts the sentence the
  opening mark is offered for; a mark ending an address still ends a sentence
- Spanish repeated-word check matches composed and decomposed spellings
- regressions: check_text with the Spanish fixture and NFC/NFD equivalents
  (acceptance and ranges), words() and composed(), URLs ending in /? beside
  ordinary questions

CodeRabbit review: Session::region_spanish takes the ISO 639-2 `spa` as
Spanish, so a `spa-MX` region writes es-MX documents.
2026-10-23 08:43:44 -03:00
aiongg 99f4511e6d docking: document native windows 2026-10-23 08:27:19 +00:00
aiongg 06c1fc8825 docking: DockDriver stands in for OS windows, and native window tests
with_native_host draws native windows as their own egui frames with a
desktop rect, focus, close requests and the commands they receive. Tests
cover each way into a native window, closing, stable ids, place and
monitor, restore, the bar drag, hiding and forwarded keys.
2026-10-23 08:27:19 +00:00
aiongg 799ab4e691 docking: every float opens in its own OS window
With native-windows on a backend with viewports, each floating window
whose panels allow it is native: in-window floats open over their place
in the app window, and restored windows go back to their monitor or onto
the main window's. Windows stay on top and hide while the app is
inactive. A thin bar with title and close replaces the OS title bar and
moves the window; closing the window closes its panels. Keys a native
window leaves unused come back in DockOutput::forwarded_events, and
DockArea::monitors takes the monitor list. Move Panel to New Window goes,
since every float is native.
2026-10-23 08:27:13 +00:00
aiongg 9c869166e8 docking: floating windows keep a stable id and store their monitor
Floating::id names a window across panel moves: fresh from Floating::new
and on load, unique within a loaded layout, kept by a whole window that
moves and by the receiver of a merge. Floating::monitor and monitor_size
record where a native window is; Action::SetMonitor sets them. Equality
ignores the id.
2026-10-23 08:26:56 +00:00
aiongg 4cdf3dece4 hayro-interpret: name the spot alternate helper for its space 2026-10-23 08:25:41 +00:00
aiongg fdf121325c ci: build and test craft-i18n with collate 2026-10-23 08:15:24 +00:00
aiongg 72a1dc7728 i18n: the shipped-locale collation test checks each locale's own order 2026-10-23 08:15:24 +00:00
aiongg 6ea2d2c7f2 i18n: standalone Hangul jamo are letters in search, not syllable parts 2026-10-23 08:15:24 +00:00
aiongg f0ed593d9f keymap: update Cargo.lock for the egui dev-dependency with serde 2026-10-23 08:14:06 +00:00
aiongg 135981286f keymap: a layout switch forgets what letter positions typed, and Layout::forget_letters does so on request 2026-10-23 08:03:27 +00:00
aiongg 5050d5dead Merge remote-tracking branch 'ao/craft-keymap' into keymap-layout-rule 2026-10-23 08:02:46 +00:00
aiongg 3f5f57642a Merge remote-tracking branch 'upstream/main' into keymap-layout-rule
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	README.md
2026-10-23 08:02:46 +00:00
aiongg fe4a4450f7 keymap: compile Keymap::twins_in only with the ui feature, its one user 2026-10-23 07:28:24 +00:00
aiongg bd5c21682c hayro: DeviceCMYK luminosity masks take their values from the inks
A luminosity soft mask whose group is DeviceCMYK draws the group's four process plates over the backdrop and applies 1 - min(1, 0.3c + 0.59m + 0.11y + k) (ISO 32000-2 §10.4.2.2), so K 100% hides the masked object. Spot colorants on those plates paint through their alternate space (Plate::spots_as_process).
2026-10-23 07:26:21 +00:00
aiongg 0ccb6a9304 keymap: ForeignSet is non_exhaustive and built with ForeignSet::new, with_name and with_truncated 2026-10-23 07:19:55 +00:00
aiongg 78639905f7 Merge remote-tracking branch 'ao/craft-keymap' into keymap-ui 2026-10-23 07:18:38 +00:00