Commit Graph

1109 Commits

Author SHA1 Message Date
Nathaniel Ledford bb50127de4
LRC-LIB-SMARTCOLL: report saved smart albums whose rules no longer check
Rules are checked when they are made, but a saved smart album can go
bad later. The usual case is a rule testing an album that is then
deleted; it silently matches nothing.

Catalog::smart_album_problems(id) checks a saved album's rules as they
are now (RuleSet::upgrade, then RuleSet::check). It is empty for a sound
smart album, a plain album or no album. albums.list reports `problems`
for every smart album, e.g. ["rule 1: no album 8"].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 31abbcee4f
LRC-LIB-SMARTCOLL: rename and rules of a smart album in one step
The rule editor renamed an album (album.rename) and then set its rules
(album.setRules) as two commits. If the second failed (refused rules
before the dialog checked them, or a save error), the album kept the new
name without the rules, and undo needed two steps.

album.setRules takes an optional `name` (blank keeps the album's) and
commits the rename and the rules as one Op::Batch: both or neither, one
undo step. The dialog edits through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 920658c76d
LRC-LIB-SMARTCOLL: rule editor never rewrites a rule by drawing it
From review: the number spinner was fixed, but the "in the last" spinner
still clamped a stored N on first draw ("0 days" became 1, hiding the
problem and saving a different rule; 20,000 became 10,000). Values
that weren't an {n, unit} object or a two-item "between" were replaced
by the defaults as soon as they were drawn.

- In the last: shown as stored ({n, unit}, any case, or a plain number
  of days) and written only when the N or the unit is changed, with
  clamp_existing_to_range(false).
- Between: anything but a pair is shown as the default pair but kept
  until one bound is edited, so the check marks it. A slice pattern
  replaces the [0] / [1] indexing.
- Album picker: reads the id as the catalog does; names albums with
  their folder ("Trips / Rome") and sorts them by name.
- Opening an album's rules applies RuleSet::upgrade.
- New tests: drawing leaves five unreadable values as stored and marks
  the three that can't match, and an edit with bad rules doesn't rename
  the album (neither OK nor confirm). The parity note no longer
  overclaims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 850311a884
LRC-LIB-SMARTCOLL: one reading of Album rule ids; folders refused; old album operators upgraded
From review:
- rules::album_rule_id reads an Album rule's id as a whole number,
  written as 3, 3.0 or "3". The check, the matcher and the editor now
  agree; the picker showed a valid "1" as unset.
- A folder holds albums, not photos, so an Album rule naming one matched
  nothing. The check now refuses it ("is a folder; choose an album in
  it").
- Smart albums saved while Album was a number field could use ≥, ≤ or
  between, which the matcher always read as "isn't". Since Album became
  is / isn't only, editing or filtering with them was refused.
  RuleSet::upgrade rewrites them to the "isn't" they meant; merge_filter
  (album.createSmart / setRules) and library.filter apply it before
  checking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford e077172f36
LRC-LIB-SMARTCOLL: document the rule editor's problem marks and album picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 7b7807b499
LRC-LIB-SMARTCOLL: rule editor marks bad rules, blocks OK and picks albums from a list
The dialog showed only the first problem, in place of the match count,
without saying which rule it was about. OK stayed clickable, and an
edit renamed the album before its rules were refused.

- Each rule with a problem gets the message right under its row, in the
  caution colour; inside groups too, and on an empty group's frame. The
  rules_editor::Env carries the problems from RuleSet::check, and each
  row finds its own by path. Markers register `ruleProblem:<salt>`.
- The summary reads "Fix the marked rules to save this album." and OK is
  disabled while any problem remains.
- confirm_dialog refuses problems before anything changes (not even the
  name), and the control channel's ui.dialog.confirm keeps the editor
  open on that error, as the button does.
- Album is its own field kind: is / isn't only (it was a number field
  that offered ≥ and between). It picks its album from a list of the
  plain albums (`ruleAlbum:` / `ruleAlbumItem:<id>:`) and starts with
  none chosen ("choose an album"). album.ruleFields reports kind "album".
- Found while testing: the number editor's spinner clamped a stored
  value outside its range as soon as it was drawn, so "Rating is 9"
  quietly became "Rating is 5". It now uses
  clamp_existing_to_range(false): the range limits dragging, and a
  stored value stays as it is, marked by the check.

New labels (Choose an album…, No albums yet, Fix the marked rules…) are
translated in all seven locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 2d29dde4d0
LRC-LIB-SMARTCOLL: a stale album rule no longer drops the filter bar from the view
From review: view_rules merged the filter bar's settings onto a smart
album's rules through merge_rules, which now also checks the rules, and
fell back to the bare album rules on any problem. A smart album testing
an album that was later deleted therefore lost the filter bar (e.g.
Rating ≥ 3) from Create Smart Album from Filter and the dialog's count,
without a word.

merge_filter does the merge alone; merge_rules is merge_filter plus the
check, for album.createSmart / setRules. view_rules uses merge_filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 3c80695e97
LRC-LIB-SMARTCOLL: rule check and matcher agree on units, ratings and label names
From review of the value checks (false rejections and one silent
mismatch):
- In-the-last units: one table (unit_secs) for check and matcher. Any
  case, singular or plural; no unit means days. An unknown unit is
  refused and now matches nothing, where the matcher used to read it as
  days, so "week" meant one day.
- Rating: refused only when no rating 0–5 could meet the rule, tested
  with the matcher's own comparison ("is 9", "≥ 7", "between 6 and 9").
  "< 6" is broad but valid. Message: "no rating 0–5 is ≥ 7".
- Catalog::label_from_name compares with Unicode lowercase, as rules
  do, so a custom label "Été" is found as "été".
- Empty text hints at the operator that fits: "isn't empty" for
  isn't / doesn't contain, "is empty" otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford b0eba68303
LRC-LIB-SMARTCOLL: document rule value checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford b5fe62e426
LRC-LIB-SMARTCOLL: check every smart-album rule value against its field
RuleSet::problems only checked that a field and operator exist (and,
lately, yes/no values). Any other value was accepted and silently
matched nothing or everything: "Rating ≥ abc", a date of "banana" or
"2026-13", "Color Label isn't pink" (everything), "Title contains ''"
(everything), "in the last 0 days", an album that is gone.

RuleSet::check(&Catalog) -> Vec<Problem> replaces it. Each Problem has
the rule's position (path [1, 0]; shown as "rule 2.1: …"), so the
editor can point at the row. It checks:
- numbers (rating 0–5), shutter speeds (1/250, 2)
- dates: 2026, 2026-08, 2026-08-14 or a full time; real dates only
- between: two bounds; dates in order (2026-08-14 … 2026-08 is fine)
- in the last: N > 0 and a known unit
- choices: the ids, the flag's picked / rejected, a colour label's
  custom name (from the catalog), copyright-status spellings
- text and keyword / people rules: something to look for, or "is empty"
- album: exists and is a plain album
- no empty groups (an empty rule list at the top stays "all photos")

album.createSmart, album.setRules and library.filter report every
problem. The editor's date defaults move from "" (which, as "between",
matched every dated photo) to the current year, and its test now
requires every default to be valid, except text and album rules, which
wait for input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford 7ba1470940
LR-LIB-CAPTURETIME: refuse capture-time shifts beyond 10,000 years
Crash fix. photo.setCaptureTime turned `shift` / `hours` into seconds
with a saturating cast and added them with plain `+`. A shift of 1e30
seconds overflowed. Only the last-resort panic guard caught it,
reporting "failed unexpectedly: attempt to add with overflow". A large
but not overflowing shift reached the catalog as a date like
-114079456599-08-23.

A shift that isn't a number or is more than 10,000 years either way is
now refused up front ("the shift is more than 10,000 years") and
nothing changes. The regression test requires that message, so a
caught panic no longer passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:57 -04:00
Nathaniel Ledford a08d0f6b03
LRC-LIB-SMARTCOLL: "in the last N" can't overflow; shift_iso stays in years 0–9999
Crash fix. A huge or negative "in the last N" from an agent or a saved
file ({"n": -1e30}) panicked with "attempt to negate with overflow" in
debug builds. Release builds wrapped instead, and the rule matched the
wrong photos.

- last_secs: a zero, negative, NaN or unreadable N is None (the rule
  matches nothing, either way round). Spans are capped at 10,000 years,
  which means "ever": every dated photo is in it and none is outside it.
- dates::shift_iso: checked_add, and a result outside years 0000–9999
  (which iso_seconds can't read back) is None. It never overflows,
  whatever the seconds.

Regression tests cover both and panicked before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 62aef59178
LRC-LIB-SMARTCOLL: document Megapixels measuring the cropped photo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 9bac3abb4b
LRC-LIB-SMARTCOLL: Megapixels rule counts the cropped photo
Megapixels used the original width × height, while Long Edge, Short
Edge and Aspect Ratio measure the photo as shown (oriented and
cropped). One rule set could disagree with itself: a 24 MP photo
cropped to a 4000 × 4000 square had a long edge of 4000 but still
counted as 24 MP. Megapixels now uses Photo::shown_size too, so that
crop is 16 MP. Rotation doesn't change it.

Behaviour change: a smart album with a Megapixels rule can now leave
out heavily cropped photos it used to include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 5865b4c019
LRC-LIB-SMARTCOLL: localization-ja example uses the new field labels
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 65516b721c
LRC-LIB-SMARTCOLL: Japanese yes/no field labels are plain nouns
With the values now あり / なし, the labels "Has Edits" (編集あり) and
"Has GPS" (GPS情報あり) read "edits: yes … none". They are only
used as smart-album field labels, so they become 編集 and GPS情報 ("編集 …
あり / なし"). The untranslated In a Stack and Cropped become スタック and
切り抜き. A test requires every yes/no field to have a Japanese label
without あり / なし.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 451e3cd28c
LRC-LIB-SMARTCOLL: document message contexts for translators
localization.md explains "<context>|<English>" catalog keys and the one
context today (smart album value); localization-ja.md notes あり / なし.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford c42fc7ac24
LRC-LIB-SMARTCOLL: Japanese yes/no rule values read あり / なし
Yes/no rule values used the shared "Yes" / "No" messages, which
Japanese translates as the answers はい / いいえ. As the value of a
condition ("編集あり … いいえ") that reads oddly; あり / なし ("has" /
"has none") is natural.

Messages are keyed by their English text, so the same English word
can't carry two Japanese translations. A small context lookup fixes
that: tr_ctx_in tries "<context>|<source>" first and falls back to the
plain message. Only ja.json adds "smart album value|Yes" / "|No"; the
shared Yes / No (はい / いいえ) stay as they are elsewhere, and every
other language keeps its plain Ja / Sí / Да. A test rejects contextual
keys that name no real value, so a typo can't silently fall back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford fa982a4cc6
LRC-LIB-SMARTCOLL: rule editor keeps an unreadable yes/no value
From review: the editor rewrote a yes/no value that was neither yes nor
no (written by an agent or an older version) to true on every frame, so
the dialog never showed the problem and OK silently saved "Yes",
turning a rule that matched nothing into one that matches. The dropdown
now shows such a value as written and changes it only when the user
picks Yes or No. Until then the dialog reports the problem and OK
refuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford d3730a7024
LRC-LIB-SMARTCOLL: library.filter checks its rule set
From review: album.createSmart and setRules refuse an unknown field, a
missing operator or a yes/no value that is neither yes nor no, but
library.filter applied such a rule set and showed an empty grid. It now
returns the same problems as an error and leaves the current filter in
place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 07d5bc3152
LRC-LIB-SMARTCOLL: yes/no rule values accept 1.0 and 0.0
From review: an agent sending a float (1.0) was refused while 1 was
accepted. Any number equal to 1 or 0 now reads as yes or no; other
numbers (2, 0.5, -1) are still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford b0de6d3fef
LRC-LIB-SMARTCOLL: document Yes / No values and the "false" fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 7d3c61c56b
LRC-LIB-SMARTCOLL: yes/no rule values read Yes / No and mean what they say
Yes/no fields (Has Edits, Cropped, Has GPS, Virtual Copy, In a Stack,
Best of Similar Shots) showed "true" / "false" in the editor and
summaries, and Russian had no translation for either. Matching read the
value with as_bool().unwrap_or(true), so an agent's "false" (a string)
or any other non-boolean counted as yes, while the summary said
"false".

- catalog: bool_value is the one reading. true / "yes" / "true" / 1 /
  no value mean yes; false / "no" / "false" / 0 mean no (strings in any
  case). Anything else matches nothing, and RuleSet::problems reports it
  ("`edited` is yes or no, not \"maybe\""), so album.createSmart and
  setRules refuse it. bool_label gives "Yes" / "No"; describe() reads
  "has edits is yes".
- ui: i18n::bool_text for the editor's dropdown and translated
  summaries (Ja / Nein…, existing keys in all locales). The editor
  stores the value back as a plain boolean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 4c826246c3
LRC-LIB-SMARTCOLL: own labels for treatment Color and label None
From review: the treatment choice "Color" reused the shared "Color" key,
which zh-hans and ru translate as the colour noun (颜色, Цвет) for the
Color panel, giving "Treatment is colour". The colour-label choice
"None" was masculine in Spanish (Ninguno) next to the feminine
"etiqueta". They are now "In Color" and "No Label", translated in all
seven locales (彩色, Цветное, Sin etiqueta…).

Also moves display_label_gaps_are_reported's doc comment back onto it;
the new choice test had been inserted between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 26df5ab60b
LRC-LIB-SMARTCOLL: document readable choice labels
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 12e1105861
LRC-LIB-SMARTCOLL: readable, translated labels for smart-album choice values
Choice values showed their rule ids in English ("Copyright Status is
publicDomain", "Treatment is monochrome"), and Russian had no
translation for any of them. Kind::Choice now carries (id, label) pairs
like the operators: rules still store and accept the ids, and people
see the labels.

- catalog: choice_label(field, id); RuleSet::describe uses the label
  ("copyright status is public domain").
- engine: album.ruleFields keeps `choices` as ids and adds
  `choiceLabels` {id: label}. A test pins the ids, since the new tuple
  type would otherwise serialize as pairs.
- ui: i18n::choice_text is the one path for the editor's value dropdown
  and translated summaries. Aspect labels are "Landscape (wide)" /
  "Portrait (tall)", because the existing "Landscape" / "Portrait" keys
  mean scenery and people. Seven locales gain Video, Copyrighted, Public
  Domain, Unknown, the aspect labels and Square (ru also Raw). A test
  requires every choice label to be translated in every language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 6b46c98654
LRC-LIB-SMARTCOLL: stronger rule-editor tests
The field-menu UI test now clicks a group row instead of hovering it,
guarding against the combo popup closing on that click (touch input,
fast clicks). default_values_make_valid_rules now checks each default
value's shape (none, pair, choice, number, a real shutter time) as its
earlier commit message claimed; before, it only checked operators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford c755656bfb
LRC-LIB-SMARTCOLL: dot files have no extension; reuse CropGeometry::is_identity
From review: `.hidden` reported the extension "hidden", so it matched
"File Extension is hidden". A name whose only dot leads has no extension
now. Photo::is_cropped uses the existing CropGeometry::is_identity
instead of comparing against a default crop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 5d4eea5f7d
LRC-LIB-SMARTCOLL: document grouped rule menu and new fields
parity.md: LRC-LIB-SMARTCOLL lists the 47 fields by menu group and what
Classic has that is still missing. LR-LIB-SMARTALBUM no longer claims
"match-all only, no rule editor". What's New describes the grouped menu
and the new fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 5d86976b82
LRC-LIB-SMARTCOLL: Any Searchable Text covers state, alt text, people, copy name
The any-text rule already searched the city and country but skipped the
state / province. It now also searches the alt text, the people on faces
and a virtual copy's name, so it agrees with the dedicated fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 5fa77aa878
LRC-LIB-SMARTCOLL: file, size, location, description and develop rules
New smart-album fields, each in its menu group:
- Source: Copy Name, In a Stack
- File: File Extension (case and leading dot ignored), Video Duration
- Description: Alt Text
- Location: City, State / Province, Country (Location still matches all)
- Size: Long Edge, Short Edge, Aspect Ratio (landscape / portrait /
  square within 1 %), measured on the photo as shown (oriented and
  cropped)
- Develop: Cropped (crop or straighten), Treatment (color / monochrome)

Photo::extension, Photo::shown_size and Photo::is_cropped hold the
domain rules. A crop rectangle that is NaN or outside the frame is
clamped, so hostile settings can't produce NaN sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 9101757bfb
LRC-LIB-SMARTCOLL: Shutter Speed rule
Camera Info gains Shutter Speed, compared in seconds and written as the
camera shows it: "≤ 1/60" finds faster exposures, "between 1/1000 and
1/125" works, and a value that isn't a time matches nothing. The editor
takes it as text (1/250, 0.5, 2").

The shutter parser moves from engine export into the catalog
(parse_shutter_seconds) so export and the rule share it. A new
rules-editor test checks that every field × operator default value
makes a valid rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford e54acbd176
LRC-LIB-SMARTCOLL: Keyword Count, People and People Count rules
Smart albums can test how many keywords a photo has ("2 or more", "2 or
fewer", "exactly 2" via the number operators), who is in it (People: the
names on face regions, with the keyword operators) and how many people.

Photo::keyword_count counts a hierarchical keyword once and ignores case
and blank entries; Photo::people is the domain rule for "people in a
photo" (named face regions, trimmed, case-insensitive, pets and unnamed
faces excluded), now shared by the existing person filter. Keyword and
people rules share one name-list matcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford 8c0b70b37f
LRC-LIB-SMARTCOLL: field menu with submenus in the rule editor
The rule editor's field dropdown shows Rating, Pick Flag, Color Label and
Any Searchable Text at the top, then a separator and one submenu per
field group instead of one long flat list. The group holding the current
field is shown in bold. Picking a field from a submenu closes the menu
and keeps the operator only when the new field has it. Widget ids
ruleField / ruleFieldGroup / ruleFieldItem let the control channel drive
it, and group names join the i18n display-label gap report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Nathaniel Ledford adcc2830dd
LRC-LIB-SMARTCOLL: group smart-album rule fields for the field menu
FIELDS is now listed in field-menu order, with TOP_LEVEL_FIELDS (rating,
pick flag, colour label, any searchable text) followed by FIELD_GROUPS
(Source, File, Date, Keywords & People, Description, Camera Info,
Location, Size, Develop, Assisted Culling). field_group / field_label
look a field up. album.ruleFields reports each field's group so agents
see the same structure as the editor. Rule ids are unchanged, so saved
smart albums keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:54:56 -04:00
Brandon Thomas 65ad290e89
Merge pull request #512 from storytold/claude/integrate-batch-17
Integrate #489, #493, #495, #494, #444, #484, #477
2026-10-09 09:25:11 -04:00
Brandon Thomas e111734532 Merge remote-tracking branch 'origin/main' into claude/integrate-batch-17 2026-10-09 09:24:54 -04:00
Brandon Thomas 8f924a9e60
Merge pull request #511 from storytold/claude/integrate-batch-16
Integrate #453, #470, #482, #492, #433, #465
2026-10-09 09:08:47 -04:00
Brandon Thomas 081a619c51 Merge PR #477 2026-10-09 09:04:07 -04:00
Brandon Thomas 7e350eccb8 Merge PR #484 2026-10-09 09:04:06 -04:00
Brandon Thomas 8abcd2fc54 Merge PR #444 2026-10-09 09:04:05 -04:00
Brandon Thomas b7331d6c13 Merge PR #494 2026-10-09 09:04:04 -04:00
Brandon Thomas 5bd4302037 Merge PR #495 2026-10-09 09:04:03 -04:00
Brandon Thomas 1b4e646df5 Merge PR #493 2026-10-09 09:04:02 -04:00
Brandon Thomas 9fef5ac702 Merge PR #489 2026-10-09 09:04:01 -04:00
Brandon Thomas 11e739cd02 Merge PR #465 2026-10-09 08:34:42 -04:00
Brandon Thomas 7317e64d74 Merge PR #433
# Conflicts:
#	crates/engine/src/media.rs
2026-10-09 08:34:41 -04:00
Brandon Thomas ceb35048bb Merge PR #492
# Conflicts:
#	crates/engine/src/media.rs
2026-10-09 08:34:40 -04:00
Brandon Thomas f731922e16 Merge PR #482
# Conflicts:
#	crates/engine/src/media.rs
2026-10-09 08:34:38 -04:00
Brandon Thomas a18a6e6a5e Merge PR #470 2026-10-09 08:34:37 -04:00