Skip to content

Consistent settings behaviour#133

Merged
Clydingus merged 6 commits into
feat/quark-engine-macos-v2from
consistent-settings-behaviour
May 8, 2026
Merged

Consistent settings behaviour#133
Clydingus merged 6 commits into
feat/quark-engine-macos-v2from
consistent-settings-behaviour

Conversation

@philpax
Copy link
Copy Markdown
Contributor

@philpax philpax commented May 7, 2026

Toggling the engine backend mid-session did not reset the server; instead, it went to the pause menu. I realised that the logic for choosing to restart or reset the server was all over the place, which meant that this bug would keep reoccurring. To fix this, I refactored the associated code to institute one source of truth. Afterwards, while testing it, I discovered that there were several bugs with offline mode.

Single source of truth for restart classification

Every Settings field now has an associated class (none / live / session / process) declared once in src/types/settings.ts. All downstream code responsible for controlling the server now consults that map, ensuring consistency of behaviour.

The classifier supports both top-level keys (covering whole subtrees) and dot-paths (when siblings disagree, e.g. debug_overlays.action_logging is live while its UI-only siblings default to none).

Lifecycle bugs surfaced while testing the refactor

A handful of pre-existing races and gaps showed up once mid-session restart was actually being exercised:

  • Race between server stop and warm-reconnect: The respawn flow fired stop+transition in the same tick, so the warm flow saw a stale "server still running" snapshot, attached to the doomed server, and threw "Server exited before becoming ready". Now waits for stop to land before transitioning.
  • Offline-mode server spawn bombed on dependency resolve: In offline mode uv couldn't reach the index to verify the lockfile and bailed out, even though the venv was already correct. The server spawn now skips the resolve+sync passes when offline.
  • Connection-lost overlay stuck around through respawn: It was only cleared on main-menu entry, so a process-class respawn left it visible all the way back into streaming. Now also cleared on LOADING entry, and suppressed entirely during intentional restarts.

Diagnostic clarity

The check-engine-status dependency-validation step now surfaces uv's actual stderr/stdout when it fails, instead of silently returning false. Saves a round of guessing the next time a venv ends up out of sync.

philpax added 6 commits May 7, 2026 23:33
Settings whose changes need a restart used to live in three drifting
hand-written lists (useEngineRespawn for process-class, useSessionInit
for session/live, plus EngineTab.hasChangesRequiringRestart for the
modal trigger). Adding engine_backend to the session list was missed,
so toggling it mid-session saved without a reset and dropped back to
the pause menu.

SETTING_CLASSES in types/settings.ts is now the single source of
truth, with helpers in utils/settingsClassifier consuming it.
Top-level keys cover whole subtrees; dot-paths split when siblings
differ (debug_overlays.action_logging is live; siblings are UI-only).
Toggling a process-class setting mid-stream raced: useEngineRespawn
called stopServer() fire-and-forget then transitioned to LOADING
immediately. The warm flow read stale isServerRunning=true from React
state, picked attachToRunningStandalone, and threw "Server exited
before becoming ready" while the doomed server finished dying.

Awaiting stopServer lets the IPC "server exited" event propagate to
isServerRunning=false before LOADING fires, so the warm flow correctly
picks bootStandalone and starts a fresh server with the new env vars.
In offline mode uv can't reach the index to verify the lockfile and
fails with "No solution found when resolving dependencies" — even
though the venv is already correctly populated from the previous
online sync.

Setting UV_NO_SYNC=1 (which also implies --frozen) inside getOfflineEnv
skips both the resolve and sync passes on uv run, so the server just
exec's python in the existing venv. Online mode is untouched: uv run
still auto-syncs when pyproject changes.
The connectionLost flag was only cleared in MAIN_MENU, so once set it
stuck through LOADING and back into STREAMING. Hit visibly when a
process-class respawn (useEngineRespawn) fires its disconnect from
outside the lifecycle reducer — the brief failed-connection render
inside STREAMING latched connectionLost true, and entering LOADING
didn't clear it.

Clearing on LOADING entry is symmetric with the existing
clearEngineErrorOnLoadingEntry: any LOADING transition is "starting
fresh", so prior overlays should reset. Also fixes the click-reconnect
recovery path, which previously kept the overlay up until the user
backed out to main menu.

(Follow-up: the overlay still flashes for a frame during a respawn
because the disconnect lands in a STREAMING render before LOADING
transitions in. To suppress it entirely, the reducer needs to know the
respawn is intentional — separate change.)
Toggling a process-class setting mid-stream briefly flashed the
"Connection Lost" overlay. The session-class reconnect path already
suppresses the overlay via `intentionalReconnectInProgress`, but
process-class respawns are driven by useEngineRespawn (outside the
reducer) and never set that flag.

Rather than adding a parallel `intentionalRespawnInProgress` boolean
plus parallel `currentProcessSig` / `lastAppliedProcess` payload
fields plus parallel detection branches, collapse all of it through a
single discriminator:

- New `RestartSignatures = { session, process }` bundle in the
  classifier; `getRestartSignatures(settings)` builds both at once.
- `useSessionInit` returns `lastApplied: RestartSignatures | null`
  (one piece of state instead of two).
- Payload carries `currentSignatures` + `lastAppliedSignatures`.
- Reducer state replaces `intentionalReconnectInProgress` with
  `intentionalRestart: 'reconnect' | 'respawn' | null`. A new
  `computeIntentionalRestart()` picks the strongest applicable
  intent (process beats session) and returns null when nothing's
  pending.
- Side-effect dispatch keys off the discriminator: `'reconnect'`
  fires the existing effect chain, `'respawn'` is silent because
  useEngineRespawn owns those side effects. Suppression checks
  become `intentionalRestart !== null`.
@philpax philpax requested a review from Clydingus May 7, 2026 22:48
@Clydingus Clydingus merged commit ee38ac8 into feat/quark-engine-macos-v2 May 8, 2026
6 of 8 checks passed
@Clydingus Clydingus deleted the consistent-settings-behaviour branch May 8, 2026 12:51
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.

2 participants