BUG: null ProjectItem deref in `EditSnapshot::refresh()` — every `EditFileOperations` construction, for hosts not using `ProjectManager` projects

# Crash: null ProjectItem deref in `EditSnapshot::refresh()` — every `EditFileOperations` construction, for hosts not using `ProjectManager` projects

**Engine:** `develop` @ `494e91d2ff5` (3.5.0). Still present at the time of writing.

**Platform:** macOS 13.7.8, AppleClang 14.0.3 — but this is not platform-specific.

## Summary

`EditSnapshot::refresh()` guards the `ProjectItem` on one line and then dereferences it

unguarded on the next:

```cpp

void EditSnapshot::refresh()

{

clear();

auto pi = engine.getProjectManager().getProjectItem (sourceFile);

...

name = pi ? pi->getName() : sourceFile.getFileNameWithoutExtension(); // guarded

setState (newState, TimeDuration::fromSeconds (pi->getLength())); // not guarded

refreshFromState();

}

```

`modules/tracktion_engine/model/edit/tracktion_EditSnapshot.cpp:227`

If the Edit has a file but no `ProjectItem`, `pi` is null and this crashes.

## Why it matters more than it looks

The deref itself is long-standing, but it used to be unreachable. `getEditSnapshot()` was

keyed by `ProjectItemID` and bailed out early on an invalid one:

```cpp

if (itemID.isInvalid())

return {};

```

Since `f0964b0a7da` (“EditClip: Fixed up more path related issues…”, 2026-03-20) snapshots

are keyed by file, and the early-out is now:

```cpp

if (editFile == juce::File())

return {};

```

That only catches an *absent* file. An Edit that has a real file but no `ProjectItem` now

sails past the guard, constructs an `EditSnapshot`, and the constructor calls `refresh()`

immediately.

## Call chain

Nothing exotic is required to reach it — merely constructing `EditFileOperations` does:

```

EditFileOperations::EditFileOperations (Edit&)

└─ SharedDataPimpl (e)

  └─ SharedEditFileDataCache::get (edit)

      └─ std::make_shared<Data> (edit)

          └─ member init:

             EditSnapshot::getEditSnapshot (edit.engine, edit.editFileRetriever())

              └─ new EditSnapshot (engine, file)

                  └─ refresh()          ← null deref

```

So any host that sets `Edit::Options::editFileRetriever` to a real file, but manages its

own storage rather than going through `ProjectManager`, crashes on **every save** — that

is where `EditFileOperations` is normally constructed.

## Reproduce

1. Create or load an `Edit` with an `editFileRetriever` returning a real file on disk.

2. Do not register that file with `ProjectManager` (no `ProjectItem` for it).

3. Construct `EditFileOperations (edit)` — e.g. call `writeToFile()`.

Crashes in `EditSnapshot::refresh()`. In a debug build it surfaces first as the

`jassert (referencedObject != nullptr)` in `ReferenceCountedObjectPtr::operator->`

(`juce_ReferenceCountedObject.h:406`).

## Suggested fix

Guard the length exactly the way the name above it is already guarded:

```cpp

setState (newState, TimeDuration::fromSeconds (pi != nullptr ? pi->getLength() : 0.0));

```

We have been running this locally with no ill effects. `refreshFromState()` follows

immediately, and for a host in this situation nothing consumes the snapshot length.

It may also be worth reconsidering whether `getEditSnapshot()` should return `{}` when

there is no `ProjectItem` for the file, which would restore the previous behaviour more

faithfully than patching the deref — but the one-line guard is the minimal fix.

## Context

Found while moving a host app from a 3.1-era pin to current `develop`. The app stores

Edits in its own bundle format rather than as `ProjectManager` projects, which is why it

hits this on the first save after the bump.

Thanks. Fixed here: