Files
firefrost-services/docs/code-bridge/responses/MSG-2026-04-13-v110-architecture.md
Claude 3906303754 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)
2026-04-13 04:35:14 +00:00

8.4 KiB

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:

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:

// 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.

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):

{
  "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):

{
  "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:

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:

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):

// 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 💙🔥❄️