feat(serverRotation): latency-aware server selection with 404-safe rotation#52
Conversation
…ing, and 404-aware rotation Agent-Logs-Url: https://github.com/Moonrend/Classworks/sessions/d254def7-bda7-413a-83b1-c553c1571523 Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
… serverRotation Agent-Logs-Url: https://github.com/Moonrend/Classworks/sessions/d254def7-bda7-413a-83b1-c553c1571523 Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR enhances Classworks Cloud server selection by making rotation error-aware (avoid rotating on 4xx), adding latency-based preferred server caching with background probing, and updating the effective server URL to use the cached preference. It also includes widespread Vue template formatting/slot syntax cleanup and a small ESLint globals update to support the new probing implementation.
Changes:
- Update
serverRotationto rotate only on network/5xx errors, and to prefer/cached the fastest reachable server via background probes. - Replace hardcoded Classworks Cloud base server selection with cached preference in
getEffectiveServerUrl. - Apply broad Vue template formatting normalization and add
AbortControllerto ESLint globals.
Reviewed changes
Copilot reviewed 64 out of 71 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/serverRotation.js | Latency-aware preferred server caching, 4xx-safe rotation logic, background probing/TTL behavior |
| src/pages/socket-debugger.vue | Template formatting normalization |
| src/pages/settings.vue | Template formatting normalization |
| src/pages/list/index.vue | Template formatting normalization |
| src/pages/list/[id].vue | Template formatting normalization |
| src/pages/index.vue | Template formatting normalization |
| src/pages/examschedule.vue | Template formatting normalization (incl. placeholder escaping) |
| src/pages/exam-editor/[id].vue | Template formatting normalization |
| src/pages/debug.vue | Template formatting normalization |
| src/pages/debug-socket.vue | Template formatting normalization |
| src/pages/debug-init.vue | Template formatting normalization |
| src/pages/cses2wakeup.vue | Template formatting normalization |
| src/pages/authorize.vue | Template formatting normalization |
| src/pages/CacheManagement.vue | Template formatting normalization |
| src/pages/404.vue | Template formatting normalization |
| src/components/settings/cards/ThemeSettingsCard.vue | Minor option API reordering + template formatting |
| src/components/settings/cards/SubjectManagementCard.vue | Slot syntax normalization + template formatting |
| src/components/settings/cards/RefreshSettingsCard.vue | Template formatting normalization |
| src/components/settings/cards/RandomPickerCard.vue | Template formatting normalization |
| src/components/settings/cards/KvDatabaseCard.vue | Template formatting normalization |
| src/components/settings/cards/HomeworkTemplateCard.vue | Slot syntax normalization + template formatting |
| src/components/settings/cards/EditSettingsCard.vue | Template formatting normalization |
| src/components/settings/cards/EchoChamberCard.vue | Lifecycle hook ordering + template formatting |
| src/components/settings/cards/DisplaySettingsCard.vue | Template formatting normalization |
| src/components/settings/cards/DataProviderSettingsCard.vue | Template formatting normalization (rotation integration context) |
| src/components/settings/cards/CloudNamespaceInfoCard.vue | Template formatting normalization |
| src/components/settings/cards/BackgroundSettingsCard.vue | Template formatting normalization |
| src/components/settings/TeacherListCard.vue | Template formatting normalization (incl. placeholder escaping) |
| src/components/settings/StudentListCard.vue | Template formatting normalization |
| src/components/settings/SettingsExplorer.vue | Template formatting normalization |
| src/components/settings/SettingItem.vue | Slot syntax normalization + template formatting |
| src/components/settings/SettingGroup.vue | Template formatting normalization |
| src/components/settings/NotificationSoundSettings.vue | Template formatting normalization |
| src/components/settings/CloudMigrationDialog.vue | Slot syntax normalization + template formatting |
| src/components/settings/AboutCard.vue | Template formatting normalization |
| src/components/home/HomeworkGrid.vue | Minor option API reordering + template formatting |
| src/components/home/HomeActions.vue | Template formatting normalization |
| src/components/home/ExamScheduleCard.vue | Template formatting normalization |
| src/components/home/ConciseExamCard.vue | Template formatting normalization |
| src/components/error/404.vue | Template formatting + minor router call simplification |
| src/components/common/HomeSkeleton.vue | Template formatting normalization |
| src/components/common/AsyncLoadingPlaceholder.vue | Template formatting normalization |
| src/components/auth/TokenInputDialog.vue | Template formatting normalization |
| src/components/auth/ProgressiveRegisterPage.vue | Template formatting normalization |
| src/components/auth/FirstTimeGuide.vue | Template formatting normalization |
| src/components/auth/DeviceAuthDialog.vue | Template formatting normalization |
| src/components/auth/AlternativeCodeDialog.vue | Template formatting normalization |
| src/components/attendance/AttendanceSidebar.vue | Template formatting normalization |
| src/components/attendance/AttendanceManagementDialog.vue | Template formatting normalization |
| src/components/UrgentTestDialog.vue | Template formatting normalization |
| src/components/UrgentNotification.vue | Template formatting normalization |
| src/components/TimeCard.vue | Template formatting normalization |
| src/components/StudentNameManager.vue | Template formatting normalization |
| src/components/SettingsLinkGenerator.vue | Watcher reordering + template formatting normalization |
| src/components/SettingsCard.vue | Template formatting normalization |
| src/components/ReadOnlyTokenWarning.vue | Template formatting normalization |
| src/components/RateLimitModal.vue | Template formatting (introduces a rendering bug with divider scope) |
| src/components/RandomPicker.vue | Template formatting normalization |
| src/components/PwaInstallCard.vue | Template formatting normalization |
| src/components/NoiseMonitorCard.vue | Template formatting normalization |
| src/components/MessageLog.vue | Template formatting normalization |
| src/components/HomeworkEditDialog.vue | Template formatting normalization |
| src/components/HitokotoSettings.vue | Template formatting normalization |
| src/components/HelloWorld.vue | Template formatting normalization |
| src/components/GlobalMessage.vue | Template formatting normalization |
| src/components/FloatingToolbar.vue | Template formatting normalization |
| src/components/CacheManager.vue | Template formatting normalization |
| src/components/AppHeader.vue | Slot syntax normalization + template formatting |
| src/App.vue | Template formatting normalization |
| eslint.config.js | Add AbortController to globals for new probing code |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function updateServerPreference() { | ||
| if (serverPreference.probing) return; | ||
| serverPreference.probing = true; | ||
| try { | ||
| const results = await Promise.all( | ||
| CLASSWORKS_CLOUD_SERVERS.map(async (url) => ({ | ||
| url, | ||
| latency: await probeServer(url), | ||
| })) | ||
| ); | ||
| const reachable = results | ||
| .filter((r) => r.latency < Infinity) | ||
| .sort((a, b) => a.latency - b.latency); | ||
| if (reachable.length > 0) { | ||
| setCachedPreference(reachable[0].url); | ||
| } | ||
| } catch { | ||
| // Probe failure is non-fatal; keep existing preference | ||
| } finally { | ||
| serverPreference.probing = false; | ||
| } |
There was a problem hiding this comment.
updateServerPreference() only updates cachedAt when at least one server is reachable. If all probes fail (e.g. outage/CORS), cachedAt stays 0 so getOrderedCloudServers() will trigger a new background probe on every call, causing repeated HEAD requests and undermining the intended 5‑minute TTL. Consider recording a lastProbedAt/updating cachedAt even on failure (or adding backoff) to avoid continuous probing during outages.
| // For HTTP 4xx errors the server is alive — propagate immediately without rotation | ||
| if (!shouldRotateOnError(error)) { | ||
| triedServers[triedServers.length - 1].status = "client-error"; | ||
| if (hasCallback) { | ||
| onServerTried({ url: serverUrl, status: "client-error", error, tried: [...triedServers] }); | ||
| } | ||
| throw error; |
There was a problem hiding this comment.
In the non-rotating (4xx) path, the last triedServers entry is marked as client-error but its .error field is never populated. Callers that display per-server failure details (e.g. mapping s.error) will lose the status/message for the final attempt. Consider storing a normalized error string on that entry as well (similar to the failed path).
| @@ -27,12 +44,11 @@ | |||
| {{ request.method }} {{ request.path }} | |||
| </v-list-item-subtitle> | |||
| </v-list-item> | |||
| </v-list | |||
| > | |||
| </v-list> | |||
| <v-divider | |||
| v-if="index < activeRequests.length - 1" | |||
| class="my-3" | |||
| ></v-divider> | |||
| /> | |||
There was a problem hiding this comment.
<v-divider v-if="index < activeRequests.length - 1" ... /> is outside the v-for that defines index, so index is undefined here and this will either fail template compilation or behave incorrectly. Move the divider inside the loop (or wrap the item+divider in a <template v-for> block) so index is in scope.
Server rotation blindly retried all errors (including 404) across every server, always started from the same fixed server, and re-probed on every request with no caching.
Changes
404 ≠ rotation trigger —
shouldRotateOnError()now only rotates on network errors or HTTP 5xx. A 4xx response means the server is healthy; propagate immediately instead of wasting a round-trip on the backup.Latency-based preference —
probeServer()/updateServerPreference()concurrently HEAD-probes all cloud servers and caches the fastest reachable one.getOrderedCloudServers()puts the preferred server first without blocking the caller.5-minute TTL cache, no polling — Preference is stored in a module-level
serverPreferenceobject. Stale cache triggers a fire-and-forget background re-probe; callers are never blocked. Real-traffic successes intryWithRotationalso update the cache viasetCachedPreference().getEffectiveServerUrlnow returns the cached preferred server instead of always hardcodingCLASSWORKS_CLOUD_SERVERS[0].