fix: address design review ACT NOW items (6 risk gaps)
- Add migrate_config() to ConfigGenerator protocol for schema version upgrades - Add per-server operation lock to ProcessManager to prevent start/stop races - Add busy_timeout retry/backoff strategy (exponential: 1s, 2s, 4s) for DB lock exhaustion - Add ConfigForm testing strategy and error boundary for malformed schemas - Add schema cache invalidation on adapter version change - Add ConfigMigrationError to typed adapter exceptions
This commit is contained in:
41
FRONTEND.md
41
FRONTEND.md
@@ -895,6 +895,14 @@ frontend/
|
||||
- Optimistic locking (send `config_version` on save)
|
||||
- 409 conflict resolution (show diff, allow override/merge)
|
||||
- Validation errors from adapter (field-level, from Pydantic)
|
||||
- **Error boundary**: if `jsonSchemaToFields()` encounters an unsupported or malformed schema type (deeply nested objects, unknown formats), render a fallback "This section uses an unsupported field type. Edit raw JSON." with a raw JSON editor instead of crashing the form
|
||||
|
||||
**ConfigForm test plan** (critical — highest-risk component):
|
||||
- **Unit: `jsonSchemaToFields`** — test every supported type mapping (string→text, integer→number, boolean→toggle, enum→select, array→repeatable), test sensitive field masking, test unknown type → fallback descriptor with `type: "raw_json"`
|
||||
- **Unit: `jsonSchemaToFields`** — test malformed schema input (missing `properties`, nested `$ref`, `oneOf`/`anyOf`) → returns fallback descriptor, never throws
|
||||
- **Integration: `ConfigForm`** — render with a 2-section schema, verify field rendering, toggle a boolean, verify dirty state, submit and verify request payload
|
||||
- **Integration: 409 conflict** — after save, mock a 409 response, verify ConflictDialog appears with diff
|
||||
- **Integration: error boundary** — mount `ConfigForm` with a schema that has unsupported `type: "object"` nested 3 levels deep, verify raw JSON fallback renders instead of crash
|
||||
|
||||
**`LogViewer`** — performance-critical:
|
||||
- Virtualized rendering (react-window or similar)
|
||||
@@ -929,7 +937,7 @@ All API data uses TanStack Query with appropriate stale times:
|
||||
| Mods | 5min | — |
|
||||
| Bans | 2min | — |
|
||||
| Game types | 30min | — (rarely changes) |
|
||||
| Config schema | 30min | — (tied to adapter version) |
|
||||
| Config schema | 30min | — (tied to adapter version) **Invalidation**: when `GET /games/{type}` returns a different `schema_version` than previously cached, TanStack Query invalidates all config schema queries for that game type. This prevents stale forms after an adapter update. |
|
||||
|
||||
**Optimistic updates** on:
|
||||
- Server start/stop → immediately update status in cache, rollback on error
|
||||
@@ -1071,7 +1079,7 @@ const { hasMissions, hasMods, hasRemoteAdmin } = useCapability(serverId);
|
||||
interface FieldDescriptor {
|
||||
name: string;
|
||||
label: string;
|
||||
type: "text" | "number" | "boolean" | "select" | "textarea" | "password" | "array";
|
||||
type: "text" | "number" | "boolean" | "select" | "textarea" | "password" | "array" | "raw_json";
|
||||
default?: unknown;
|
||||
min?: number;
|
||||
max?: number;
|
||||
@@ -1087,6 +1095,9 @@ function jsonSchemaToFields(
|
||||
): FieldDescriptor[] {
|
||||
// Walk schema.properties, map each to a FieldDescriptor
|
||||
// Mark fields in sensitiveFields as type: "password"
|
||||
// Unknown types (nested objects, oneOf/anyOf, $ref) → type: "raw_json"
|
||||
// The form renders a textarea with JSON editing for these fields
|
||||
// rather than crashing or hiding the field silently
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1105,6 +1116,31 @@ function jsonSchemaToFields(
|
||||
|
||||
Future adapters register themselves; the card list auto-populates from `GET /games`.
|
||||
|
||||
### Schema Cache Invalidation
|
||||
|
||||
Adapter config schemas are cached with `staleTime: 30min`. When an adapter is updated and its `schema_version` changes, the frontend must not serve a stale schema. The invalidation pattern:
|
||||
|
||||
```typescript
|
||||
// When fetching game type info, compare schema_version
|
||||
function useGameTypeWithInvalidation(gameType: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useQuery({
|
||||
queryKey: ["gameType", gameType],
|
||||
staleTime: 30 * 60 * 1000,
|
||||
onSuccess: (data) => {
|
||||
const prevVersion = localStorage.getItem(`schema_version_${gameType}`);
|
||||
if (prevVersion && prevVersion !== data.schema_version) {
|
||||
// Adapter was updated — invalidate all config schema queries
|
||||
queryClient.invalidateQueries({ queryKey: ["configSchema", gameType] });
|
||||
localStorage.setItem(`schema_version_${gameType}`, data.schema_version);
|
||||
} else if (!prevVersion) {
|
||||
localStorage.setItem(`schema_version_${gameType}`, data.schema_version);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling UX
|
||||
@@ -1121,6 +1157,7 @@ Future adapters register themselves; the card list auto-populates from `GET /gam
|
||||
| WS disconnect | StatusBar indicator + stale data with timestamp |
|
||||
| WS reconnect | Automatic; no user action needed |
|
||||
| Server crashed | Toast notification + status dot turns red |
|
||||
| Malformed adapter schema | Raw JSON fallback in ConfigForm section ("This section uses an unsupported field type. Edit raw JSON.") |
|
||||
|
||||
**No `alert()` calls.** All feedback uses toast (transient) or inline (persistent) patterns.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user