Finish the mobile performance pass

- Cache the model catalog once per user and each conversation's setting
  separately; clear both on logout and after model saves
- Compress hashed assets asynchronously with a byte budget and set Vary
- Memoize message parsing and throttle selection reads
- Debounce draft writes to localStorage
- Keep the composer editor on the page and focused, as before
- Add a bundle budget check and run it in CI
- Show a loading block while sessions load and a workspace avatar in the
  mobile conversation header
This commit is contained in:
2026-10-20 14:37:11 +01:00
parent c1a94fc9a5
commit 4f7e0a6bf0
38 changed files with 1320 additions and 334 deletions
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import Form from "@moduletrace/twui/form/Form";
import useFormInit from "@/src/hooks/use-form-init";
import type { HarnessOverview, HarnessSettingsFormObject } from "@/src/types";
@@ -20,6 +20,11 @@ type Props = {
* The last save's message, kept by the page across the remount.
*/
notice?: string;
/**
* Whether there are unsaved edits, so a background refresh can leave
* them alone.
*/
onDirtyChange?: (dirty: boolean) => void;
};
/**
@@ -30,6 +35,7 @@ export default function HarnessSettingsForm({
harnesses,
save,
notice: saved_notice,
onDirtyChange,
}: Props) {
const saved = toHarnessForm({ harnesses });
/**
@@ -47,6 +53,10 @@ export default function HarnessSettingsForm({
const changed = changedHarnessSettings({ form, saved });
const dirty = changed.length > 0;
useEffect(() => {
onDirtyChange?.(dirty);
}, [dirty, onDirtyChange]);
const discard = () => {
setForm(saved);
setStatus({});