LoadingButton was merged into the core Button component in MUI v7.
Replace all usages and remove the @mui/lab dependency entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove @babel/core, @babel/preset-env, @babel/preset-react,
@babel/preset-typescript (Vite uses SWC via @vitejs/plugin-react-swc),
http-proxy-middleware (Vite has built-in proxy), and ts-node (unused).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
React 19:
- Replace JSX.Element with React.JSX.Element (namespace moved)
- Update RefObject types to include null
- Remove forwardRef from FullScreenVideo (ref is a regular prop now)
- Fix snackbar test to use await act() for fake timer advancement
testing-library:
- @testing-library/dom 8 → 10
- @testing-library/react 13 → 16
- Use ReactNode instead of ReactElement in testutil
react-router:
- Rename react-router-dom → react-router (package consolidated in v7)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default trailingComma changed from "es5" to "all", so many files
are reformatted with trailing commas in more positions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bump @emotion/react, @emotion/styled, react-hook-form,
@testing-library/jest-dom, msw, eslint-plugin-react, and
@testing-library/user-event within their current major versions.
Fix App.test.tsx race condition exposed by msw 2.12: wait for the
fetch error to render before test cleanup aborts the request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FullScreenVideo was a plain function component, so MUI's Modal/FocusTrap
could not attach a ref to it, producing three React warnings. Wrap it in
React.forwardRef and forward the ref to the outermost Box element.
Also fix an MUI warning about the modal content node not accepting focus,
by adding tabIndex={-1} to FullScreenVideo's root element.
The live view WebSocket handler was declared async and called
`await blob.arrayBuffer()` directly:
```js
onWsMessage = async (e: MessageEvent<any>) => {
...
const raw = new Uint8Array(await e.data.arrayBuffer());
...
};
```
When several WebSocket messages arrive in quick succession—as commonly
happens at stream start—the browser dispatches multiple concurrent
invocations of this handler. Each invocation independently awaits
`Blob.arrayBuffer()`. `LiveCamera.tsx` implicitly assumed that message
n's buffer would resolve before message n+1's buffer, which seemed
logical because they're on the same TCP stream. However, there's no
guarantee of this, and it apparently is not true on Firefox. Perhaps
it's about how FF schedules async tasks.
`LiveCamera.tsx` assumes in its timestamp handling that media segments'
blobs will be strictly resolved in the order they should be played.
Playing segments before their dependencies (e.g. non-IDR frames before
the first IDR frame) doesn't go well.
Fix: make `onWsMessage` synchronous and chain each blob onto a
sequential promise (`messageChain`) so that `processWsBlob` is always
called in arrival order, regardless of the implementation-defined
resolution order of individual `Blob.arrayBuffer()` calls.
Fixes#343.
TypeScript 5.7 made typed arrays generic (Uint8Array<TArrayBuffer extends
ArrayBufferLike>), which is no longer assignable to BufferSource (used by
SourceBuffer.appendBuffer), since ArrayBufferLike includes SharedArrayBuffer.
Since the data always comes from Blob.arrayBuffer(), it is always backed by a
plain ArrayBuffer, so use Uint8Array<ArrayBuffer> to reflect that precisely.
...to:
* 20 (maintenance LTS)
* 22 (maintenance LTS)
* 24 (active LTS release)
* 25 (current non-LTS release)
And address some "realm mismatch" problems in tests:
* It was using `AbortSignal` from `jsdom`'s realm, `fetch` from the Node
native `fetch`. Node 24 upgraded to undici v7 with stricter Fetch spec
compliance, which complained about this.
* It was doing `instanceof` checks from `myfetch` which failed for
similar reasons.
by updating vitest (see <https://github.com/vitest-dev/vitest/issues/8374>) and simplifying the `api.ts` checking.
Along the way, give error messages like `POST /api/login failed; see browser console` rather than the unhelpful `TypeError: Failed to fetch`.
* in backend, save the full reason, not just the most direct error,
which is often the useless `UNKNOWN`
* in UI, instead of wrapping in `<Typography>` which just resorts in
a weird ransom-note mixed size look, reserve space for or actually
use an icon.
Users are often puzzled why there are short recordings. Previously
the only way to see this was to examine Moonfire's logs. This should
be a much better experience to find it right in the UI where you're
wondering, and without the potential the logs are gone.
Fixes#302
Fixes#286.
I dug into Firefox code a bit to understand this but got a lost. But
I guess there are different policies for what's accessible via
`video.src = ""` than for `<video src="">`. This works for now, matches
what other players such as `mpegts.js` do, and is closer to what Safari
MME (required on iPhone) needs anyway. (With MME, apparently you have to
use `video.srcObject`, or the `MediaSource`'s `sourceopen` event will
never fire.)
* The `DisplaySelector` wasn't getting the correct flex layout.
Before this was done by a manual style on a `Paper` element. That
broke when adding the inner `<CardContent>` to because that's the
container that needs the `display: "flex"`. But really, it's clearer
to do this with `<FormGroup>` anyway, so do that.
* Switch from `<Card><CardContent>` to `<Paper sx={{ padding: ... }}>`.
The card content has extra padding (16 px in general, 24 at the bottom
of the last element to fit with an action). I'm not quite sure the
best way to remove it, and the simpler `<Paper>` seems fine for this
use anyway.
Noticed this while looking at these `act` warnings. I didn't manage to
solve those, but at least this makes the tests more consistent with
current docs.
In the upgrade I managed to dust off some tests that I'd been skipping
for quite a while. It turns out one of them was pointing out a real
problem: in the network error case, we didn't display the error to the
user properly. It's really sad this reaches our code as a `TypeError`,
but it is what it is.