Skip to content

ci: bump the actions group with 3 updates - #1

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions-646988e673
Open

ci: bump the actions group with 3 updates#1
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions-646988e673

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 27, 2026

Copy link
Copy Markdown

Bumps the actions group with 3 updates: actions/checkout, actions/upload-artifact and actions/download-artifact.

Updates actions/checkout from 4 to 7

Release notes

Sourced from actions/checkout's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: actions/checkout@v6.0.3...v7.0.0

v6.1.0

What's Changed

https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/ for more details about this breaking change

Full Changelog: actions/checkout@v6.0.3...v6.1.0

v6.0.3

What's Changed

New Contributors

Full Changelog: actions/checkout@v6...v6.0.3

v6.0.2

What's Changed

Full Changelog: actions/checkout@v6.0.1...v6.0.2

v6.0.1

What's Changed

... (truncated)

Changelog

Sourced from actions/checkout's changelog.

Changelog

v7.0.1

v7.0.0

v6.0.3

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

... (truncated)

Commits

Updates actions/upload-artifact from 4 to 7

Release notes

Sourced from actions/upload-artifact's releases.

v7.0.0

v7 What's new

Direct Uploads

Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

New Contributors

Full Changelog: actions/upload-artifact@v6...v7.0.0

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: actions/upload-artifact@v5.0.0...v6.0.0

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

... (truncated)

Commits
  • 043fb46 Merge pull request #797 from actions/yacaovsnc/update-dependency
  • 634250c Include changes in typespec/ts-http-runtime 0.3.5
  • e454baa Readme: bump all the example versions to v7 (#796)
  • 74fad66 Update the readme with direct upload details (#795)
  • bbbca2d Support direct file uploads (#764)
  • 589182c Upgrade the module to ESM and bump dependencies (#762)
  • 47309c9 Merge pull request #754 from actions/Link-/add-proxy-integration-tests
  • 02a8460 Add proxy integration test
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • Additional commits viewable in compare view

Updates actions/download-artifact from 4 to 8

Release notes

Sourced from actions/download-artifact's releases.

v8.0.0

v8 - What's new

[!IMPORTANT] actions/download-artifact@v8 has been migrated to an ESM module. This should be transparent to the caller but forks might need to make significant changes.

[!IMPORTANT] Hash mismatches will now error by default. Users can override this behavior with a setting change (see below).

Direct downloads

To support direct uploads in actions/upload-artifact, the action will no longer attempt to unzip all downloaded files. Instead, the action checks the Content-Type header ahead of unzipping and skips non-zipped files. Callers wishing to download a zipped file as-is can also set the new skip-decompress parameter to true.

Enforced checks (breaking)

A previous release introduced digest checks on the download. If a download hash didn't match the expected hash from the server, the action would log a warning. Callers can now configure the behavior on mismatch with the digest-mismatch parameter. To be secure by default, we are now defaulting the behavior to error which will fail the workflow run.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

Full Changelog: actions/download-artifact@v7...v8.0.0

v7.0.0

v7 - What's new

[!IMPORTANT] actions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

New Contributors

Full Changelog: actions/download-artifact@v6.0.0...v7.0.0

v6.0.0

... (truncated)

Commits
  • 3e5f45b Add regression tests for CJK characters (#471)
  • e6d03f6 Add a regression test for artifact name + content-type mismatches (#472)
  • 70fc10c Merge pull request #461 from actions/danwkennedy/digest-mismatch-behavior
  • f258da9 Add change docs
  • ccc058e Fix linting issues
  • bd7976b Add a setting to specify what to do on hash mismatch and default it to error
  • ac21fcf Merge pull request #460 from actions/danwkennedy/download-no-unzip
  • 15999bf Add note about package bumps
  • 974686e Bump the version to v8 and add release notes
  • fbe48b1 Update test names to make it clearer what they do
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions

Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 4 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v7)

Updates `actions/upload-artifact` from 4 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

Updates `actions/download-artifact` from 4 to 8
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v4...v8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot @github

dependabot Bot commented on behalf of github Aug 27, 2026

Copy link
Copy Markdown
Author

Labels

The following labels could not be found: ci, dependencies. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

thatcube added a commit that referenced this pull request Aug 30, 2026
…ed-render Done safety, document/bytes atomicity, opening-overflow deferral

Finding #1 (HIGH, app.rs ~4624): `card_retention` is keyed per card, not
per revision. A committed Done left stale `(retained, exported)` flags
from before the edit, so a subsequent Save/auto-close/overflow fast path
could trust pre-edit export history and dismiss the card without ever
exporting the newly committed redacted revision.
  Fix: `App::commit_card_output` now resets `card_retention` to
  `(false, false)` for the card once the commit is successfully posted,
  forcing any later overflow/auto-close to re-export against the
  revision Done just committed. `fn save()` always writes to a fresh
  export location, so this reset is always safe (never overwrites or
  reuses a prior export path).

Finding #2 (HIGH, host.rs ~1079): if `editor.render()` failed on Done,
`show_editor` still fell through to close the window, persist the
document, and call `editor_closed(..., committed = true)` -- deferred
outputs would receive an uncommitted snapshot while the card's own
bytes kept the original, unredacted pixels.
  Fix: a failed render on Done now calls the new `EditorWindow::reopen()`
  and `continue`s past commit/persist/close entirely, keeping the editor
  open and surfacing the failure via `tracing::warn!` instead of ever
  behaving as though the edit succeeded. `show()` has already closed the
  native viewport for the frame the instant it decided `Done`, so
  `reopen()` is what asks the next frame to draw the window again.

Finding #3 (HIGH, pipeline.rs ~1579 / ~2387): `commit_rendered` updated
the vault's flattened bytes but not the durable, derived `DocumentData`.
If document persistence failed or lagged, `Worker::open()` could still
reconstruct from stale pre-edit/unredacted history, and a later editor
session could then export or display it.
  Fix: `commit_card_output` now threads the exact `DocumentData` the
  render was produced from into `derived_documents`, monotonically
  overwritten on every successful commit (never on Cancel/failure).
  `Worker::open()` now prefers `derived_documents` over durable history
  whenever an entry exists, so a reopen can never resurrect a document
  older than the bytes already committed. `commit_rendered` backfills
  `editor_source` from the pre-edit pixels the first time a card commits
  without one (the common case when Smart Frame was off at capture),
  preventing later reconstruction from double-applying annotations on
  top of an already-flattened render.

Finding #4 (MEDIUM, app.rs ~2373): `CardEvent::Overflow` only checked
for a live editor, not `opening_cards` -- an overflow landing between a
queued Open and its `Outcome::Opened` could retire (and, per
`handle_overflow`'s no-live-editor branches, potentially dismiss) a card
whose editor was still being created, permanently stranding it as
Editing with its timer paused and Close disabled.
  Fix: the Overflow guard now also defers while the card is in
  `opening_cards`, mirroring the existing `AutoClose` sibling. The
  existing `Outcome::Refused` handler already drains both
  `deferred_auto_close` and `deferred_overflow` correctly, so no other
  drain-path changes were required.

Tests added:
- app.rs: `a_committed_done_resets_stale_retention_so_the_next_overflow_re_exports`,
  `an_overflow_racing_a_fresh_open_defers_instead_of_retiring_the_opening_card`
- pipeline.rs: `a_first_commit_backfills_editor_source_from_the_pre_edit_pixels`,
  `opening_a_committed_card_never_resurrects_a_stale_durable_document`
- host.rs: `a_failed_done_render_reopens_instead_of_closing` (structural,
  matching the file's existing convention for `show_editor`'s internal
  ordering)
- scrozz-ui/editor/mod.rs: `reopen_undoes_a_close_show_already_committed_for_this_frame`

Validation:
- cargo fmt -p scrozz -p scrozz-ui: clean
- cargo test -p scrozz --bin scrozz: 799 passed
- cargo test -p scrozz-ui (lib + integration + doctests): all green
- cargo clippy --workspace --all-targets --all-features -- -D warnings: clean

Preserves root viewport/automatic-termination lease/no-native-close
invariants and dirty-close prompting; no changes to AppKit-owned window
classes/delegates or native-close paths. Branch remains isolated; no
rebase, merge, or push into any shared integration branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
…ed Done/persist/retention

Round 5 (final acceptance round) required Done, Save, and history-only
Release to become generation+revision *acknowledged*, not enqueue-and-close,
closing a set of races where a stale or refused async outcome could publish,
retain, or discard the wrong revision of an edited capture.

1. HIGH (host.rs): Done previously closed and refreshed the card thumbnail as
   soon as `CommitCardOutput` was *posted*, not once it was actually
   committed. A worker that delayed, refused, or hit a missing vault entry
   left a window where drag/plain Copy/Save/Upload could still read the
   original (pre-edit, possibly unredacted) cached bytes even though the UI
   already looked "done". `show_editor` now defers Done's finalize (window
   close, thumbnail refresh) behind a `PendingEditorClose` that only
   resolves once the exact `(generation, revision)` commit acknowledgement
   arrives; a refusal reopens/retains the editor and surfaces the failure
   instead of silently finalizing on stale bytes.

2. HIGH (app.rs): `Job`/`Outcome` variants for Copy/Save/Upload/history-close
   completions carried no generation or revision, so a save that finished
   late (after Done had already moved the card to a newer editing session)
   could mark the *new* revision's retention/export flags using the *old*
   save's success, letting cleanup discard the unsaved edit as "already
   exported". Retention/export completions now carry the exact
   `(generation, revision)` they were dispatched for and are only trusted
   against `card_committed_version`, tracked synchronously at dispatch time
   (matching the worker's strict FIFO ordering) so a stale outcome can never
   supersede a newer commit, and a newer commit always supersedes an older
   one monotonically.

3. HIGH (pipeline.rs): `commit_rendered` replaced a card's flattened bytes
   and `DocumentData` without also updating the raw `editor_source` still
   associated with the old document, so a flattened-visible-fallback reopen
   could apply the new (committed) document on top of a stale raw source,
   silently reintroducing removed annotations or reversing a redaction.
   Committing a revision now atomically replaces the source alongside the
   document it was rendered from, so reopening a card can never reach an
   older document than the bytes actually committed for it, and cache and
   history can no longer disagree about which revision is current.

4. MEDIUM (app.rs): overflow accounting only inspected live/editor-only
   cards, not `opening_cards`, so an overflow evaluated in the window
   between an Open request being queued and its `Outcome::Opened` arriving
   could retire or release a card that was still mid-creation. Overflow is
   now deferred for any card still opening, exactly like it already was for
   a live editor, and drains once on either refusal or close rather than
   double-cleaning up.

Also: a history-only Release now waits for persistence's own success/
failure acknowledgement instead of releasing unconditionally right after
posting `PersistDocument` -- a refused persist (e.g. an unavailable history
store) retains and can recover the card instead of releasing it with the
edit already lost.

Audited every other output path (Copy/Save/Upload/Drag) against the same
generation/revision-acknowledgement standard: `card_output_job` and
`drag_bytes` already branch on live-editor presence, rendering the exact
open revision fresh via `editor.render()`/`get_revision(card, generation,
revision)` whenever an editor is open, and only trust the plain card cache
once no editor is open -- which Finding #1 now guarantees is safe. No
further changes were needed on those paths.

Testing:
- 6 new app.rs tests cover Finding #2's revision-aware retention gating:
  a stale save is suppressed once a newer revision is committed, a fresh
  session's first save is trusted, an exact-revision match is trusted, a
  reopened card clears its prior session's tracked version, a failed
  commit clears its own provisional version without disturbing a later
  save in the same still-open session, and a second commit supersedes the
  first monotonically.
- 4 new app.rs tests cover Findings #1/#4's acknowledgement gating: a
  pending close stays frozen until its commit ack lands and then reports
  failure (never silently succeeds); a refused persist for an editor-only
  card reports failure, not success; a live card's refused persist does
  not block its already-successful commit-only close; and a persist that
  cannot even be posted (worker gone) resolves the pending close as
  Failed instead of hanging forever.
- 1 new pipeline.rs test confirms `Outcome::UploadDone` carries the exact
  revision a cache-hit link was made from (constructed directly against
  `UploadWorker` to exercise this without a network call, since the
  default non-`cloud`-feature build's `share_artifact_impl` always errors
  end-to-end).
- Audited existing coverage for base multi-editor lifecycle invariants
  (duplicate-open focuses the existing editor, two editors stay isolated,
  dirty-close routes through the discard prompt, auto-close pauses while
  editing) and confirmed it is already exercised by prior rounds' tests;
  no gaps found requiring new coverage.
- `cargo test -p scrozz --bin scrozz`: 811 passed, 0 failed (repeated 4x
  to rule out timing flakiness in the new async-ack tests).
- `cargo test -p scrozz-ui`: 69 passed + 2 doctests, 0 failed.
- `cargo fmt -p scrozz -p scrozz-ui`.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`:
  clean (fixed 4 collapsible-if lints, 1 unnecessary-mut-passed, and 2
  unnecessary-get-then-check lints introduced by this round's own code).

Root viewport, automatic-termination lease, and no-native-close invariants
are untouched by this round; only the Recent Captures editor lifecycle and
capture-worker output paths were modified.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
…very

Finding #1 (host.rs Smart Frame/Scene analysis delivery ignored the
Done pending-close freeze): the async Smart Frame result delivery loop
in host.rs could apply an analysis result to an editor whose Done had
already captured an authoritative frame/document and was only waiting
on its commit/persist ack, advancing the frozen document's revision.
On a commit/persist failure the editor then reopened with this
post-Done mutation already applied. The delivery loop now checks the
same editor_close_pending freeze the round-6 colour-picker fix checks;
a result for a frozen card is requeued (App::requeue_smart_frame_result)
rather than applied or dropped, since the analysis can't be recomputed.
Once the freeze resolves next tick, a committed close has already
removed the editor (the requeued result finds nothing and is silently
dropped), while a failed close reopens the editor at the exact frozen
revision, so the deferred result is still valid to deliver then. Added
a host.rs meta-test mirroring the existing colour-picker freeze test.

Finding #2 (app.rs concurrent uploads had no per-request identity):
close_after_upload was a flat HashSet<CardId> and UploadRefused/
UploadDone carried no action identity beyond the pre-existing revision
check, so a stale completion or refusal for a superseded upload
request could incorrectly clear bookkeeping (or fail) meant for a
newer, still in-flight upload of the same card. Added a monotonic
upload_action: HashMap<CardId, u64> (mirroring the existing
card_committed_version pattern) plus a next_upload_action counter.
CardEvent::Upload allocates a fresh action id and unconditionally
overwrites upload_action[card] on dispatch; Outcome::UploadDone/
UploadRefused now carry that action id and are ignored (continue,
touching nothing) when it no longer matches the card's current action.
Threaded action: u64 through pipeline.rs's Job::Upload, UploadJob::Share,
queue_upload/answer_upload, and UploadWorker. upload_recorded_media
synthesizes an untracked action id, preserving its prior behaviour.
Added regression tests for a stale UploadDone/UploadRefused arriving
before the current action's own completion, verifying dismissal and
retention fire exactly once, for the current action only.

Validation: cargo fmt -p scrozz -p scrozz-ui (no changes beyond the
edits above); cargo test -p scrozz --bin scrozz (821 passed); cargo
test -p scrozz-ui (69 + 2 doctests passed); cargo clippy --workspace
--all-targets --all-features -- -D warnings (clean, zero warnings).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
Peer review round 8 raised 5 MEDIUM findings on top of round 7's
generation/revision-acknowledged Done/persistence redesign. All five are
fixed here:

1. host.rs: editor_close_pending renamed/broadened to
   editor_close_frozen, now true for a card whose close is either still
   pending or whose ack has resolved but the result has not been drained
   yet. Closes the gap where async delivery (color picker, Smart Frame)
   could mutate an editor between ack resolution and result draining.

2. app.rs: Outcome::UploadDone/UploadRefused now validate the exact
   current action id before touching status/revision logic, so a
   current-but-revision-stale outcome only clears its own identified
   state and never a differently-actioned card.

3. app.rs: upload_recorded_media now tracks its action in
   pending_upload like every other upload path (close_after: false),
   so a stale/duplicate recorded-media outcome can no longer overwrite a
   newer in-flight request's status.

4. app.rs: every CardEvent::Upload dispatch unconditionally inserts a
   fresh PendingUploadAction (action id + close-after policy read at
   that dispatch), so a superseding upload can never inherit a stale
   close-after flag left by a request it replaced.

5. pipeline.rs: UploadWorker now carries a SurfaceWaker and wakes the
   window event loop after every successful outcome send (mirroring
   HistoryReader), so success/refusal outcomes are guaranteed to drain
   promptly instead of waiting on an unrelated wake source.

Consolidated the old close_after_upload: HashSet<CardId> plus
upload_action: HashMap<CardId, u64> bookkeeping into one
pending_upload: HashMap<CardId, PendingUploadAction>. A missing entry
is now treated as stale/untracked rather than trusted by default, which
is the crux of Finding #3.

Tests: migrated all existing close_after_upload/upload_action tests
to pending_upload; fixed one regression this surfaced
(failed_upload_resumes_deferred_overflow_cleanup needed to mirror the
real UploadRefused handler's pending_upload removal before calling
fail_upload directly); added dedicated regressions for all 5 findings:
color_picker_events_freeze_a_card_whose_close_is_pending_but_always_clear_ownership
and smart_frame_delivery_freezes_a_card_whose_close_is_pending_but_never_loses_the_result
(#1, updated for the broadened freeze predicate),
a_stale_recorded_media_upload_outcome_never_overwrites_a_newer_ones_status
(#3), a_superseding_upload_dispatch_replaces_a_stale_close_after_policy
(#4), and a_successful_upload_wakes_the_window_event_loop /
a_refused_upload_wakes_the_window_event_loop (#5).

Validation: cargo build (clean, no warnings), cargo test -p scrozz --bin
scrozz (825 passed / 0 failed), cargo fmt -p scrozz (no diffs beyond
these edits), cargo clippy --workspace --all-targets --all-features -- -D
warnings (clean).

No shared-branch merge; pushed only to this isolated branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
…efusal, deterministic upload supersession

Three round-9 peer-review findings, all fixed with regression coverage:

1. HIGH app.rs ~2148: Save/Upload completion of a live edited revision was
   trusted as current whenever card_committed_version was absent for the
   card -- true not only for a session's first completion but also for one
   answering an edit the user had since Cancelled, since Cancel never wrote
   anything to card_committed_version to disagree with. Added
   card_cancelled_generation: HashMap<CardId, u64>, recording the exact
   editor generation Cancel (or a clean close) discarded, cleared on the
   editing session's own Outcome::Opened. Introduced output_version_is_stale
   as the single shared staleness test for both Outcome::Done and
   Outcome::UploadDone: a version is stale when a different revision has
   since been committed for its generation, OR that exact generation is
   known to have been cancelled. Cancel itself never touches card_retention
   (it already reflects pre-edit state, untouched), so this closes the race
   without needing any explicit restore logic.

2. MEDIUM pipeline.rs ~3055: Worker::answer_upload's local-refusal path
   (encoding failure, missing cache entry, read failure, upload worker gone)
   sent its outcome via a raw channel send, bypassing the wake-aware emit
   helper every other Worker call site already used -- so the reactive
   event loop could go unwoken on a local refusal. Routed answer_upload
   through self.emit(outcome) instead; all three upload dispatch functions
   (upload, upload_image, upload_recording) funnel through this one
   function, so the fix covers every local-refusal path at once.

3. MEDIUM app.rs ~2708/~6498: a failed superseding Upload dispatch (still
   image or recorded media) left the previous PendingUploadAction fully
   intact -- action id, close policy, all of it -- because only a
   *successful* new dispatch had ever replaced pending_upload. A later
   outcome for that now-truly-superseded action still matched pending_upload
   exactly and was applied as though the second dispatch had never
   happened. Extracted the invalidate-then-attempt logic shared by both
   dispatch sites into dispatch_upload_action: it always removes any
   existing pending_upload entry for the card *before* attempting the new
   dispatch, regardless of whether that attempt itself succeeds, so a
   failed re-dispatch leaves nothing tracked as current and any old outcome
   that still arrives falls through to the existing action-mismatch no-op.
   Both CardEvent::Upload and upload_recorded_media now call this one
   helper.

New/updated tests (app.rs):
- a_cancelled_editors_stale_save_completion_never_marks_a_reverted_card_retained
  (Finding #1): a Save dispatched against a live editor's revision, that
  editor then Cancelled, then the stale completion arrives -- must not mark
  retention, must not dismiss, must still retire its own close-after-output
  bookkeeping.
- dispatch_upload_action_invalidates_the_superseded_action_before_attempting_the_new_one
  (Finding #3): a failed re-dispatch still invalidates the prior action;
  its later late outcome falls through as superseded, not current.
- dispatch_upload_action_tracks_the_new_action_with_its_own_close_policy_on_success
  (Finding #3): a successful re-dispatch fully replaces the prior action's
  id and close policy.

New test (pipeline.rs):
- a_locally_refused_upload_wakes_the_window_event_loop (Finding #2): a
  locally-refused Job::Upload both produces Outcome::UploadRefused and wakes
  the injected SurfaceWaker.

Validation: cargo build clean; full suite 829/829 passing (826 -> 829,
+3 new); cargo fmt -p scrozz -- --check clean; cargo clippy --workspace
--all-targets --all-features -- -D warnings clean.

No changes to Settings, Scene model/editor, or Crop files. Root
viewport/automatic-termination lease/no-native-close invariants and
AppKit-owned window classes/delegates untouched. Isolated branch only --
no merge/rebase/push into any shared integration branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
…ch-failure status

Finding #1 (HIGH, app.rs ~2263/~3466): Outcome::Opened unconditionally
cleared the single per-card card_committed_version/card_cancelled_generation
scalars on every new editor open, erasing an older generation's tombstone
before every action dispatched against it had resolved. A late async
outcome for that older cancelled/superseded generation could then appear
"current" and wrongly mark retention/dismiss the card; a second Cancel
also overwrote the first tombstone; neither map was ever cleaned up on
card retirement.

Replaces both scalar maps with a single per-generation
card_generation_fates: HashMap<CardId, HashMap<u64, GenerationFate>>
(GenerationFate::Committed(revision) | Cancelled). Outcome::Opened no
longer clears anything (a fresh generation has no prior entry to
reset). CardOutputCommitFailed's rollback now removes only its own
generation's entry, pruning the outer per-card map only once it is
empty. output_version_is_stale, editor_closed's Cancel-tombstone
insert, and commit_card_output's commit-time insert are all rewired to
the per-generation map. dismiss_recent_capture now also clears a
retiring card's whole fate map.

Regression coverage: cancel gen1 -> open gen2 -> late gen1 UploadDone
is still recognised stale; multiple independently cancelled
generations each keep their own fate; dismissing a card clears its
fate map; reopening a card does not erase an older generation's
recorded fate (replaces the prior test that asserted the old, buggy
erase-on-open behavior). All five pre-existing tests touching the old
fields were updated to the new map shape.

Finding #2 (MEDIUM, app.rs ~6594): a recorded-media upload dispatch
failure cleared bookkeeping but left the card's status stuck on
"Uploading..." forever, unlike the still-image path's own round-9
fix. Extracted the dispatch-outcome handling into
report_recorded_media_dispatch_outcome so the failure branch is
directly testable (upload_recorded_media itself cannot be driven end
to end without the cloud feature, a pre-existing constraint shared
with the round-8 test on the same function); it now sets an explicit
"upload could not be started" status alongside the existing note.

card.rs: added SurfaceCall::SetStatus and wired Recording::set_status
to record it, so tests can assert on status text rather than only on
notes.

Validation: cargo test -p scrozz --bin scrozz (833/833, default
features; 838/838 with --all-features), cargo fmt -p scrozz --check
(clean), cargo clippy --workspace --all-targets --all-features -D
warnings (clean). cargo test --workspace --all-features passes aside
from two pre-existing macOS ScreenCaptureKit integration tests that
require live screen-recording permission unavailable in this
environment (scrozz-capture, unrelated to this change) and one
one-off selection.rs flake that reproduced 0/4 times on rerun.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
thatcube added a commit that referenced this pull request Aug 30, 2026
Replaces the flat close_after_output/pending_upload/card_pending_uploads
trio with a single per-card map of outstanding OutputActionId entries
(outstanding_output_actions: HashMap<CardId, HashMap<u64, OutstandingAction>>),
spanning card-level AND editor-level Copy/Save/Save-As/Upload dispatches.

Finding #1 (HIGH): copy_rendered/save_rendered previously posted their
jobs with no outstanding-action registration at all, so Cancel/Done
could prune a card's generation fate immediately while a late in-flight
result from that exact dispatch could still appear "current" against
nothing left to compare it to, or corrupt a concurrent card-level
action's shared bookkeeping. Both now allocate a fresh action id,
register it as OutputActionKind::EditorOutput (close_after: false, and
never exclusive with a concurrent card-level dispatch) on a successful
post, and every Job/Outcome variant (Copy, CopyImage, Save, SaveImage,
SaveImageTo, Upload, UploadImage) now carries that action id end to
end so only the exact dispatch it belongs to is ever resolved.

Finding #2 (MEDIUM): the upload outstanding-counter
(card_pending_uploads: HashMap<CardId, u32>) was not duplicate-safe,
and UploadRefused decremented before validating action identity. The
counter is gone; current_upload_action: HashMap<CardId, u64> tracks
only the current upload's id, and every terminal outcome resolves via
resolve_output_action(card, action), which is idempotent -- a duplicate
delivery removes nothing the second time and never re-triggers fate
pruning.

Regression matrix (all new/updated tests):
- editor Copy dispatched then Cancel; late success never touches
  retention or dismisses (an_editors_own_copy_registers_as_editor_output...)
- editor Save dispatched then a later Done commits a newer revision;
  late success never marks the older bytes retained
  (an_editors_own_save_registers_as_editor_output...)
- card-level and editor-level outputs concurrent for the same card
  resolve independently (card_level_and_editor_level_outputs_for_the_same_card_resolve_independently)
- duplicate UploadDone/UploadRefused for a superseded action are
  idempotent no-ops (a_stale_upload_success_for_a_superseded_action...,
  a_stale_upload_refusal_for_a_superseded_action...)
- a stale old action's own outcome resolves harmlessly while a newer
  action remains outstanding (dispatch_upload_action_invalidates_the_superseded_action...)
- a failed post never registers an outstanding action
- every final action prunes its card's outstanding-actions entry and
  settled generation fates (updated across the Round 9-11 upload/fate
  test suite)

Validation: cargo fmt -p scrozz -- --check clean; cargo test -p scrozz
--bin scrozz --all-features: 843 passed, 0 failed; cargo clippy
--workspace --all-targets --all-features -- -D warnings clean.

No shared integration -- isolated branch only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b9c730c6-15b6-4599-8879-92fbe37495e8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants