Bridge: dispatch — v1.1.0 full architecture plan from Gemini consultation
7 priorities in order: 1. File ID comparison (foundational) 2. Date-time seeding heuristic 3. Zero-click widget + Recalibrate dropdown 4. is_ignored flag 5. BCC log parsing Plus: manifest version audit results (5 servers have version data)
This commit is contained in:
236
docs/code-bridge/responses/MSG-2026-04-13-v110-architecture.md
Normal file
236
docs/code-bridge/responses/MSG-2026-04-13-v110-architecture.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Chronicler Dispatch — v1.1.0 Full Architecture Plan (Gemini Consultation Complete)
|
||||
|
||||
**Date:** April 13, 2026
|
||||
**From:** Chronicler #84 — The Meridian
|
||||
**To:** Code
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Gemini consultation complete. Michael has reviewed and approved the plan.
|
||||
Full consultation: `firefrost-operations-manual/docs/consultations/gemini-modpackchecker-ux-overhaul-2026-04-12.md`
|
||||
|
||||
No time pressure — Michael wants to get this right. These are v1.1.0 priorities in order.
|
||||
|
||||
---
|
||||
|
||||
## Priority 1: File ID Comparison (Foundation)
|
||||
|
||||
**The problem:** We're comparing messy display name strings ("ATM10-6.5" vs "ATM10-6.6"). Unreliable and ugly.
|
||||
|
||||
**Gemini's fix:** Use sequential File IDs from CurseForge/Modrinth. `latest_file_id > current_file_id` = update available. Clean, reliable, platform-agnostic.
|
||||
|
||||
**Schema migration needed:**
|
||||
```sql
|
||||
ALTER TABLE modpackchecker_servers
|
||||
ADD COLUMN current_file_id VARCHAR(64) NULL,
|
||||
ADD COLUMN latest_file_id VARCHAR(64) NULL;
|
||||
```
|
||||
|
||||
**API changes:**
|
||||
- `ModpackApiService::fetchLatestVersion()` should also return `file_id`
|
||||
- CurseForge: use the file's `id` field
|
||||
- Modrinth: use the version's `id` field
|
||||
- FTB: use the version `id`
|
||||
- Technic: use the build number
|
||||
|
||||
**Version comparison logic:**
|
||||
```php
|
||||
// Prefer file ID comparison if available
|
||||
if ($currentFileId && $latestFileId) {
|
||||
$updateAvailable = $latestFileId !== $currentFileId;
|
||||
} else {
|
||||
// Fallback to string comparison
|
||||
$updateAvailable = $currentVersion !== $latestVersion;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Priority 2: Date-Time Seeding Heuristic
|
||||
|
||||
**The problem:** First run seeds `current_version = latest_version`, which is wrong for servers that have been running old versions.
|
||||
|
||||
**Gemini's fix:** On first detection, fetch the platform's file history. Find the release closest to (but not after) `modpack_installations.created_at`. That's the assumed current version.
|
||||
|
||||
```php
|
||||
private function seedCurrentVersion(string $platform, string $modpackId, ?string $installDate): array
|
||||
{
|
||||
if (!$installDate) {
|
||||
// No install date — fall back to latest
|
||||
return $this->apiService->fetchLatestVersion($platform, $modpackId);
|
||||
}
|
||||
|
||||
$allFiles = $this->apiService->fetchFileHistory($platform, $modpackId);
|
||||
|
||||
$assumedCurrent = collect($allFiles)
|
||||
->filter(fn($f) => $f['releaseDate'] <= $installDate)
|
||||
->sortByDesc('releaseDate')
|
||||
->first();
|
||||
|
||||
return $assumedCurrent ?? $this->apiService->fetchLatestVersion($platform, $modpackId);
|
||||
}
|
||||
```
|
||||
|
||||
**New method needed:** `ModpackApiService::fetchFileHistory(platform, modpackId)`
|
||||
- CurseForge: `GET /v1/mods/{modId}/files` — returns all files with dates
|
||||
- Modrinth: `GET /project/{id}/version` — returns all versions with dates
|
||||
- Returns: array of `['id', 'version', 'displayName', 'releaseDate']`
|
||||
|
||||
**Also:** If `manifest.json` exists and has a `version` field — use that directly as `current_version` instead of any heuristic. We confirmed this works on 5 servers (Mythcraft, Create Plus, Beyond Depth, Beyond Ascension, Homestead). Manifest version is truth — no guessing needed.
|
||||
|
||||
---
|
||||
|
||||
## Priority 3: Zero-Click Widget with Recalibrate
|
||||
|
||||
**The problem:** Widget requires clicking, shows only latest version string, no comparison, no context.
|
||||
|
||||
**Gemini's redesign:**
|
||||
|
||||
### New GET endpoint needed:
|
||||
`GET /api/client/extensions/modpackchecker/servers/{server}/status`
|
||||
Returns cached DB data (NO external API calls):
|
||||
```json
|
||||
{
|
||||
"configured": true,
|
||||
"platform": "curseforge",
|
||||
"modpack_name": "MYTHCRAFT 5",
|
||||
"current_version": "Update 5",
|
||||
"latest_version": "Update 5",
|
||||
"current_file_id": "6148845",
|
||||
"latest_file_id": "6148845",
|
||||
"update_available": false,
|
||||
"last_checked": "2026-04-13T04:00:00Z",
|
||||
"detection_method": "installer"
|
||||
}
|
||||
```
|
||||
|
||||
### New GET endpoint for Recalibrate dropdown:
|
||||
`GET /api/client/extensions/modpackchecker/servers/{server}/releases`
|
||||
Returns last 10 releases from platform (DOES make external API call):
|
||||
```json
|
||||
{
|
||||
"releases": [
|
||||
{"file_id": "6148845", "display_name": "MYTHCRAFT 5 | Update 5", "release_date": "2026-03-27"},
|
||||
{"file_id": "6089021", "display_name": "MYTHCRAFT 5 | Update 4.1", "release_date": "2026-03-11"},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### New POST endpoint for Recalibrate save:
|
||||
`POST /api/client/extensions/modpackchecker/servers/{server}/calibrate`
|
||||
Body: `{ "file_id": "6089021", "version": "Update 4.1" }`
|
||||
Sets `current_version`, `current_file_id`, `is_user_overridden = true`
|
||||
|
||||
### Widget TSX redesign:
|
||||
```tsx
|
||||
const ModpackVersionCard: React.FC = () => {
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data?.uuid);
|
||||
const [data, setData] = useState<StatusData | null>(null);
|
||||
const [showCalibrate, setShowCalibrate] = useState(false);
|
||||
const [releases, setReleases] = useState([]);
|
||||
|
||||
// Zero-click: load on mount from cache
|
||||
useEffect(() => {
|
||||
if (!uuid) return;
|
||||
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`)
|
||||
.then(res => setData(res.data))
|
||||
.catch(() => {});
|
||||
}, [uuid]);
|
||||
|
||||
const openCalibrate = () => {
|
||||
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/releases`)
|
||||
.then(res => setReleases(res.data.releases));
|
||||
setShowCalibrate(true);
|
||||
};
|
||||
|
||||
// Render: platform icon | current → latest | Calibrate button
|
||||
// If update_available: orange background
|
||||
// If unconfigured: gray with "Not configured"
|
||||
// If showCalibrate: dropdown showing last 10 releases to click
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Priority 4: is_ignored Flag
|
||||
|
||||
**The problem:** Vanilla, FoundryVTT, Hytale servers pollute the DB and show unconfigured widgets.
|
||||
|
||||
**Note from Michael:** Nest ID filtering does NOT work — his eggs span multiple nests.
|
||||
|
||||
**Solution:**
|
||||
```sql
|
||||
ALTER TABLE modpackchecker_servers ADD COLUMN is_ignored BOOLEAN DEFAULT FALSE;
|
||||
```
|
||||
|
||||
- Widget shows "Hide (Not a Modpack)" button for unconfigured servers
|
||||
- Clicking sets `is_ignored = true`, widget unmounts
|
||||
- Cron skips servers where `is_ignored = true`
|
||||
- Admin panel shows ignored servers in a separate list with "Restore" option
|
||||
|
||||
---
|
||||
|
||||
## Priority 5: BCC Log Parsing (Optional Signal)
|
||||
|
||||
**Research findings from live testing:**
|
||||
|
||||
- `latest.log` IS readable via `DaemonFileRepository::getContent('logs/latest.log')`
|
||||
- `BetterCompatibilityChecker` mod prints: `Loaded BetterCompatibilityChecker - Modpack: {name} | Version: {version}`
|
||||
- Mythcraft 5 has BCC but it's unconfigured (`CHANGE_ME`)
|
||||
- Most packs (ATM10 etc.) don't have BCC at all
|
||||
- Log only has startup lines if server recently restarted
|
||||
|
||||
**Recommended approach:** Add as optional detection step 4 in the cron (after modpack_installations, egg vars, file detection):
|
||||
```php
|
||||
// Step 4: BCC log parsing
|
||||
private function detectFromLogs(Server $server): ?array
|
||||
{
|
||||
try {
|
||||
$log = $this->fileRepository->getContent('logs/latest.log');
|
||||
if (preg_match('/Loaded BetterCompatibilityChecker - Modpack: (.+?) \| Version: (.+)/', $log, $m)) {
|
||||
if ($m[1] !== 'CHANGE_ME' && $m[2] !== 'CHANGE_ME') {
|
||||
return ['name' => trim($m[1]), 'version' => trim($m[2])];
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
Document in BuiltByBit: "Servers with BetterCompatibilityChecker configured will have the most accurate version detection. Version updates on server restart."
|
||||
|
||||
---
|
||||
|
||||
## Also: Manifest Version Audit Results
|
||||
|
||||
Ran Wings filesystem audit on all 22 servers for `manifest.json`:
|
||||
|
||||
| Status | Servers |
|
||||
|--------|---------|
|
||||
| ✅ Has manifest with version | Mythcraft 5 (Update 5), Create Plus (0.9.0), Beyond Depth (Ver12.3.2), Beyond Ascension (Ver2.4.1), Homestead (1.2.9.4) |
|
||||
| ⚠️ Manifest but no version field | Society, All of Create NC, Otherworld, Submerged 2 |
|
||||
| ❌ No manifest | 13 servers (installed via modpack installer) |
|
||||
|
||||
When `detectCurseForge()` finds a valid manifest, it should also extract `manifest['version']` as `installed_version` and use it as `current_version`. This is truth — no heuristic needed for these 5 servers.
|
||||
|
||||
---
|
||||
|
||||
## Summary: What Needs to Be Built
|
||||
|
||||
| Priority | Task | Complexity |
|
||||
|----------|------|------------|
|
||||
| 1 | File ID fields in DB + comparison logic | Medium |
|
||||
| 2 | fetchFileHistory() + date-time seeding | Medium |
|
||||
| 2b | manifest['version'] as current_version | Small |
|
||||
| 3 | New status/releases/calibrate endpoints | Medium |
|
||||
| 3b | Widget TSX redesign (zero-click + Recalibrate) | Large |
|
||||
| 4 | is_ignored flag + Hide button | Small |
|
||||
| 5 | BCC log parsing in cron | Small |
|
||||
|
||||
Take them in order. File ID comparison first since it's foundational.
|
||||
|
||||
*— Chronicler #84, The Meridian*
|
||||
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|
||||
Reference in New Issue
Block a user