Commit Graph
3216 Commits
Author SHA1 Message Date
aiongg eee3b799fe Merge PR #2371: #2365: centre the search field on the window, not inside the bar's margins 2026-10-22 02:37:49 +00:00
aiongg fbc927f0b1 Merge PR #2347: feat(guided): Guided Upright guides select, start on the press and drag their endpoints
docs/parity-checklist.md: both revision rows.
2026-10-22 02:37:49 +00:00
aiongg 4de80f3ef9 Merge PR #2306: LR-CROP-RECT: a moved crop slides along a straightened photo's tilted edges (#2299) 2026-10-22 02:37:36 +00:00
aiongg cb8115b239 Merge PR #2217: LR-EDIT-OPTICS-PROFILE: look up Nikon DSLR lenses by their lens specification
docs/parity-checklist.md: newer header (2026-10-21); both revision rows.
2026-10-22 02:37:35 +00:00
aiongg 2965161505 Merge PR #1997: KEY-CROP-SWAP: X in the crop tool swaps the aspect once (#1866) 2026-10-22 02:37:24 +00:00
aiongg 590c17a8f3 Merge PR #1977: GUM-3203: Stress-test previews against full-size renders 2026-10-22 02:37:23 +00:00
aiongg b242cd6bfc Merge PR #1972: Tone: neutral display tone map is the identity up to white (#1945) 2026-10-22 02:37:23 +00:00
aiongg bb18036420 Merge PR #1909: docs(support): explain the Remove tool workflow (#1756) 2026-10-22 02:37:22 +00:00
aiongg b1c4cb7df9 Merge PR #1258: Ignore hidden and empty masks when choosing develop planes. 2026-10-22 02:37:22 +00:00
aiongg fb316ee5ea Merge PR #1402: Fix slider wheel edits after leaving and returning to the Edit panel
crates/ui-egui/src/widgets/tests_numeric_entry.rs: keep both sides' tests.
docs/parity-checklist.md: newer header (2026-10-21); KEY-SLIDER-NUDGE from #1402, KEY-SLIDER-TYPE from #1687; both revision rows.
2026-10-22 02:37:21 +00:00
aiongg 92c90ad743 Merge PR #1687: KEY-SLIDER-TYPE: a long click keeps the value field open; drag the value to scrub 2026-10-22 02:36:59 +00:00
aiongg 08f9334457 Merge PR #2452: LR-PRE-PANEL: the applied preset stays marked on returning to the pho… 2026-10-22 02:36:59 +00:00
aiongg a22f447523 Merge PR #1921: Context menus: a right-click elsewhere on the same widget moves its o… 2026-10-22 02:36:58 +00:00
aiongg f4dd312eac Merge PR #2177: LRC-DEV-AUTOSYNC: sync HDR state while retaining per-photo tuning
crates/engine/src/lib.rs: keep both explicit comparison leaves (camera_local_tone and hdr).
crates/engine/src/tests.rs: keep both sides' tests.
2026-10-22 02:36:58 +00:00
aiongg 81f304a867 Merge PR #2208: LRC-DEV-AUTOSYNC: sync camera local tone Off 2026-10-22 02:36:40 +00:00
Dylan M. Taylor bfb23db2ca Use chrono instead of hand-rolled UTC conversion (#2457)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month table with chrono as a workspace dependency:
- apps/lightcraft/src/logging.rs: timestamp (log line time)
- crates/catalog/src/dates.rs: civil + weekday
- crates/catalog/src/stacks.rs: iso_seconds (date -> seconds)
- crates/engine/src/cmd/face_recognize.rs: capture_secs (native only)
- crates/meta/src/datetime.rs: DateTime::unix_seconds and the valid() day check
Output formats and semantics are unchanged (pre-epoch clamp, day rollover that
normalize_iso relies on, leap-second tolerance, EXIF/XMP parsing). chrono uses
only std, so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock as the epoch, keeping the old pre-epoch clamp.

timestamp and catalog's civil write years past 9999 with {:04} like the old
{y:04} did, because %Y would add a '+'. Tests cover the out-of-range and
5-digit-year cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01, changing output the old hand-rolled
Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was
318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).

- apps/lightcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/catalog/src/dates.rs: `civil` cycle-based (the callers stay inside
  0000-9999, but a far clock no longer resets to the epoch).

Pinned old outputs in tests:
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
          10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  civil 10_000_000_000_000 -> 318857-05-20T17:46:40,
        -10_000_000_000_000 -> -314918-08-13T06:13:20, i64 extremes

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* 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-21 19:36:36 -04:00
Dylan M. Taylor d79b1f33e3 security: enforce HTTPS on curl downloads (SonarCloud S6506) (#2493)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 19:35:59 -04:00
Bruno-Alumn c96af686a1 Merge remote-tracking branch 'upstream/main' into fix/preset-mark-per-photo
# Conflicts:
#	docs/parity-checklist.md
2026-10-21 22:42:18 +00:00
chordpli c28a537a15 LR-CROP-RECT: a crop a handle drag filled to the tilted edges still moves (#2299)
The E2E showed the previous fix missed the common case: enlarging a locked
crop until it stops leaves a frame that fills the photo's height, with its
top and bottom both at the edge of is_within_image's 1e-6 tolerance. No
translation keeps both there with room to spare, so the move stayed stuck.

When the frame can't move as it is, solve again with it shrunk about its
centre by the least that puts it strictly inside (at most 1e-5 of its size,
aspect unchanged). This also covers a frame at the edge on one side, so the
half-tolerance first pass is dropped and a click moves nothing again.
2026-10-22 07:34:48 +09:00
chordpli 89a35ded91 LR-CROP-RECT: a crop a handle drag left at a tilted edge still moves (#2299)
A handle drag stops where is_within_image's 1e-6 tolerance ends, so a
corner can sit right at it. The move solve then let that corner go 1e-9
further, the result failed the check, and the across-then-down fallback
left the frame stuck. Solve first with every corner kept within half the
tolerance, and only then where it is.
2026-10-22 07:34:48 +09:00
chordpli f443bed56f LR-CROP-RECT: trim the crop move comments (#2299) 2026-10-22 07:34:48 +09:00
chordpli 4bd40cda60 LR-CROP-RECT: a crop just past a tilted edge still moves, and a click leaves it be (#2299)
A handle drag can leave the frame up to is_within_image's tolerance past a
tilted edge. The move's bounds were tighter than that, so such a frame was
stuck again, and a click inside it moved it. The bounds are now never tighter
than where the frame is. A test pins how a smaller frame pushed past a tilted
edge slides along it, and the parity checklist's revision history notes the fix.
2026-10-22 07:34:48 +09:00
chordpli d63ce8e5f8 LR-CROP-RECT: a moved crop slides along a straightened photo's tilted edges (#2299)
On a straightened photo, a crop that fills the photo's height could not be
moved sideways: the move went across and then down, and no straight sideways
step stays inside the turned photo. The frame now goes to the place nearest
the pointer's that keeps it inside, so it slides along the tilted edges to the
photo's side. Unstraightened, that place is the old across-then-down clamp.
2026-10-22 07:34:28 +09:00
Dylan M. Taylor 3dc3b955df ci: add sonar-project.properties for CI-based analysis
Marks the project ineligible for SonarCloud Automatic Analysis, resolving the
CI-vs-automatic-analysis conflict so the SonarCloud workflow succeeds.
2026-10-21 16:00:43 -04:00
Dylan M. Taylor 511908ab06 ci: add SonarCloud scan via shared reusable workflow
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
2026-10-21 15:47:01 -04:00
Haocheng FangandClaude Opus 5.5 977bbebcdc KEY-SLIDER-NUDGE: a drag that leaves the row disarms wheel edits
The disarm waited for the button to be released outside the row, so a
drag that went out and came back kept the wheel armed. Leaving disarms
now even mid-drag, and a drag that starts outside the row does not arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:40:04 -05:00
Haocheng FangandClaude Opus 5.5 868de3a9fe KEY-SLIDER-NUDGE: disarm the wheel on any move out of the row (#1355)
Review follow-ups:
- Check every PointerMoved / PointerGone of the frame, not only where the
  pointer ended: a slow frame merged leaving the row and coming back, so
  the slider stayed armed (new test for both events).
- One global armed slider (`wheel_slider_key`) instead of a bool per
  slider: unarmed sliders no longer write context data every frame, and
  arming another slider disarms the previous one. The wheel remainder is
  cleared on arming and disarming, so the second focus check is gone.
- The sideways branch uses `pointer_in_row` too.
- Document that Tab focus arms the arrows only; move the whats-new entry
  to 2026-10-21; update the KEY-SLIDER-NUDGE row, header and history; fix
  a test comment that described the old remainder carry-over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:30:32 -05:00
Brandon Thomas ec0cda9a6f Merge pull request #2486 from storytold/release-0.11.0
Release 0.11.0
v0.11.0
2026-10-21 14:05:52 -04:00
Brandon ThomasandClaude Opus 5.5 79d916c149 Release 0.11.0
The web release now builds with craft-fonts too (web-craft-fonts), as VectorCraft's
does, so the published web app ships the Chinese and Japanese faces docs/releasing.md
promises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 14:05:43 -04:00
Haocheng Fang f8978a0b93 Fix #1355: disarm slider wheel edits after pointer leaves 2026-10-21 12:34:49 -05:00
mahmatias 77b0204331 fix(guided): the press position survives press+release in one frame
egui clears `press_origin` on the release, so a slow frame that swallows
press, move and release into one batch reports `primary_pressed` with no
origin: the gesture fell back to the release position and a real drag
wrote nothing (its length check saw a zero-length stub). The gate now
reads the primary press back from the frame's own events before that
fallback — the press decides what it means, wherever the batch ended.
2026-10-21 14:13:18 -03:00
mahmatias c19265dee0 fix(guided): the press point, not the current pointer, owns the gesture
`contains_pointer` reads where the pointer is NOW; a slow frame can
deliver the press on a menu and the move off it in the same pass, and
the gate would then pass with the saved press origin under the menu.
`layer_id_at(press_origin) == resp.layer_id` asks which layer owns the
press itself: the menu's area (or any modal) wins there and the guide
under it stays untouched. The navigator check stays for its own widget.
2026-10-21 14:13:18 -03:00
mahmatias f2e97d39fe fix(guided): the loupe's press is layer-aware, not just inside its rect
`primary_pressed` plus `resp.rect.contains` reads the raw pointer, so
anything floating above the photo — the context menu, a dropdown, a
dialog — also started a guide gesture under it. The gate now asks
`resp.contains_pointer()`, which the hit test keeps honest about
covering widgets; the navigator check stays. Two tests press on a menu
over a line and over an endpoint: nothing is selected, moved or drawn.
2026-10-21 14:13:18 -03:00
mahmatias b1c607d7ac fix(guided): the primary button and the canvas only; the press point is the gesture's own
- a secondary/middle press or one on the navigator panned the photo but
  also selected or drew a guide: the gestures take the primary button and
  skip the navigator's rect
- egui forgets the press origin by the release, so the endpoint drag
  compared against the guide's old position: a press that landed within
  the grab radius and never moved wrote a no-op guide; the gesture now
  carries the press point itself
- a selection left behind by a Delete past the last guide clears with the
  photo/tool check in crop_overlay
2026-10-21 14:13:17 -03:00
mahmatias ca26c4046a fix(guided): a stale selected index does not fire an empty guides edit; docs: un-mojibake the dashes 2026-10-21 14:13:17 -03:00
mahmatias 331e0d2103 style: fmt (blank line before point_segment_dist) 2026-10-21 14:13:17 -03:00
mahmatias aa0f9b92a7 docs(parity): LR-GUIDED-SELECT/DELETE rows land as done (press semantics) 2026-10-21 14:13:17 -03:00
mahmatias a22c496d03 feat(guided): guides start on the press and their endpoints can be dragged
A press starts what it means right away from the pointer's press point: on an endpoint (within 7 px) it drags that endpoint, committing one geometry.guides edit on release (the guide must stay at least 0.02 long; a press without a real move only selects, so a click never leaves a no-op undo entry); on a line (within 6 px) it selects the guide (a press on the selected one deselects); anywhere else it starts a new guide from the press point itself instead of where egui's drag threshold was crossed, leaving nothing selected. The endpoint being dragged is drawn bright while the pointer moves. tests_guided_guides 8/8.
2026-10-21 14:11:08 -03:00
mahmatias 7635b38c36 feat(guided): select and delete one Guided Upright guide
A click near a guide selects it (white, thicker, larger endpoints); drawing a new guide or clicking away clears the selection, and so does leaving the photo or the tool. Delete / Backspace with the tool open removes only the selected guide (geometry.guides minus its index, one undo step); with none selected it removes nothing — the photo itself is never deleted while Guided Upright is open. selected_guide is UI-only (serde skip). Coverage: tests_guided_guides 5/5.
2026-10-21 14:11:08 -03:00
aiongg e142e2ffa4 Merge PR #2470: integration of 43 reviewed PRs (round 1) 2026-10-21 12:59:32 -04:00
aiongg 22ae8bc9a8 Integration: #2280 drop a clone of the Copy crop in its test 2026-10-21 15:04:38 +00:00
aiongg 21903294bf Merge track r1-rawcat into next
crates/ui-egui/locales/{cs,it,pl}-formats.json: kept the translations and added the track's new keys.
docs/parity-checklist.md: newer header line; revision rows from both sides.
docs/whats-new.md: both tracks' 2026-10-15 entries.
2026-10-21 14:58:55 +00:00
aiongg 69fcc25535 Revert "Merge PR #995: LR-VIEW-CLIPPING: ⌥-drag previews for Blacks, Whites, Exposure, Sharpening and NR (#937)"
Held for the owner: #995 and #999 overlap, and #995's author offered on 10-12 to
close #995 once his commit was in #999. docs/parity-checklist.md: header keeps the
#1485 note and drops the #995 part.
2026-10-21 14:56:17 +00:00
aiongg a1455fa7cd Merge PR #1988: #1507: web start-up treats only NotFoundError as a missing file
apps/lightcraft-web/src/files.rs: keeps #1987's and #1988's tests
docs/whats-new.md: keeps both 2026-10-15 entries
2026-10-21 14:56:12 +00:00
aiongg e9e44643a5 Revert "Integration: #995 time drag drafts only while the GPU draws the view"
This reverts commit 5aa5c6681f.
2026-10-21 14:55:59 +00:00
aiongg 697d201e97 Merge PR #1987: #1508: web backup refuses to start while changes are unsaved 2026-10-21 14:55:55 +00:00
aiongg 047e9bb962 Integration: #1924 a fresh re-import keeps its own stem sidecar 2026-10-21 14:55:45 +00:00
aiongg 5443225c3c Merge track r1-i18nci into next 2026-10-21 14:54:41 +00:00
aiongg 4e581739bd Merge PR #1924: Do not read a sibling's shared stem sidecar on import (#1905)
docs/parity-checklist.md: header keeps the newer 2026-10-18 change
2026-10-21 14:54:19 +00:00
aiongg 523894b00b Merge PR #1998: LR-BEHAV-BG-TASKS: Reload from Disk reads its files in the background
docs/parity-checklist.md: header names #1998's change
2026-10-21 14:54:08 +00:00