305 Commits
v3.5.0 ... main

Author SHA1 Message Date
Claude Code
f4f96dfe31 Task: reaction roles + Carl-bot migration (REQ-2026-04-15-reaction-roles)
- src/discord/reactionRoles.js: REACTION_ROLE_MAP for 3 #get-roles messages (Paths, Notifications, Servers), handleReactionAdd/Remove with partial fetch + silent-fail
- src/index.js: add GuildMessageReactions + DirectMessages intents, Partials for Message/Channel/Reaction (needed for pre-cache bot messages)
- src/discord/events.js: wire messageReactionAdd/Remove handlers + guildMemberAdd (Wanderer role + welcome DM, silent-fail on closed DMs)
- src/routes/stripe.js: post-checkout link-reminder DM for Awakened+ via req.app.locals.client, non-blocking + silent-fail
2026-04-15 06:17:57 -05:00
Claude
b329951719 REQ: add Carl-bot migration scope — welcome DM, Wanderer role, link reminder DM 2026-04-15 11:14:51 +00:00
Claude
0273059148 REQ: reaction roles for #get-roles — all 3 messages, 22 emoji mappings 2026-04-15 11:09:08 +00:00
Claude
23509d700b feat: Add LuckPerms group assignment to tier sync — sets group + meta on checkout 2026-04-15 09:42:17 +00:00
Claude
6bf9e41242 feat: LuckPerms meta sync on Stripe checkout — sets maxclaims/maxchunkloaders across all online servers 2026-04-15 09:21:18 +00:00
Claude Code
224579a5ea bridge: end-of-night ACTIVE_CONTEXT — queue cleared, 2 features shipped 2026-04-15 02:16:01 -05:00
Claude
2cfda0fb2b feat: Recover servers-api Worker from Cloudflare dashboard — now in git 2026-04-15 07:11:53 +00:00
Claude Code
b0b69fb172 Task #166: Trinity Console Issue Tracker (REQ-2026-04-14-issue-tracker)
- Migration 141: issues, issue_attachments, issue_comments
- src/routes/admin/issues.js: session-auth UI routes (list/new/detail/status/assign/comments/upload)
- src/routes/api.js: /api/internal/issues REST surface (Bearer token)
- src/services/issueNotifier.js: Discord webhook helper (DISCORD_ISSUE_WEBHOOK_URL)
- Views: index (list+filters), new (mobile-first form), detail (screenshots, comments, workflow)
- layout.ejs: sidebar nav link
- package.json: add multer ^1.4.5-lts.1
- CSRF token passed via query param on multipart forms (body unparsed when csurf runs)
- Screenshots stored in services/arbiter-3.0/uploads/issues/ (10MB limit, 6 files max)
2026-04-15 01:53:18 -05:00
Claude Code
263a7e3e47 Task #163: backfill script for empty task descriptions (6/15 matched) 2026-04-15 01:46:11 -05:00
Claude Code
722771e305 bridge: archive SCC + code-queue-badge-fix; update ACTIVE_CONTEXT 2026-04-15 01:33:01 -05:00
Claude
de916c5215 fix: Update Farm Crossing 5 → Farm Crossing 6 channel mapping in status poller 2026-04-15 06:23:34 +00:00
Claude Code
3d2b6963c1 bridge: status update — now running locally on Nitro 2026-04-15 01:00:42 -05:00
Claude
fd064ed48c docs: Update CLAUDE.md — Code now running locally on Nitro (April 15, 2026) 2026-04-15 05:56:32 +00:00
Claude
86f87e71e6 feat(trinity-core): v2.5.0 — resilient session handling, /health endpoint
- Add unauthenticated /health endpoint (kills AUTH FAILED spam from health checks)
- Stale session + initialize request: ignore stale header, create fresh session
- Stale session + tool call: return 400 instead of 404 so client reads JSON-RPC payload
- Gemini-consulted architecture (gemini-trinity-core-mcp-sessions-2026-04-14.md)
2026-04-15 00:38:12 +00:00
Claude
222188edf8 Bridge request: Task Description Hygiene Pass (Task #163)
16 tasks have empty descriptions in DB but full specs exist in archived
markdown files. Code needs to backfill from docs/archive/tasks-index-archived/.

Chronicler #89
2026-04-14 22:49:46 +00:00
Claude
6efe2eaa7a Bridge request: Trinity Console Issue Tracker (Task #166)
Mobile-first issue tracker for Holly. Screenshot upload from phone,
minimal friction submission while in-game. Full schema, API routes,
and UI spec for Code.

Chronicler #89
2026-04-14 22:30:09 +00:00
Claude
e1cb136cc3 fix(trinity-core): return 404 for stale sessions after restart
When mcp-server restarts, activeSessions is cleared but Claude.ai may
still hold the old session ID. Previously this fell to a generic 400 error.
Now returns 404 with clear re-initialize message so clients know to
create a fresh session.

Chronicler #89
2026-04-14 21:30:50 +00:00
Claude
a48886e481 feat(trinity-core): v2.4.0 — add Streamable HTTP transport for Claude.ai MCP connector
Claude.ai upgraded to MCP protocol 2025-11-25 (Streamable HTTP) but Trinity Core
only supported the deprecated 2024-11-05 (HTTP+SSE) transport. This caused the
SSE connection to drop after initialize — tools/list never completed.

Changes:
- Add StreamableHTTPServerTransport alongside existing SSEServerTransport
- app.all('/mcp') handler detects protocol via mcp-session-id header
- Legacy SSE still works via GET /mcp (backwards compatible)
- New Streamable HTTP works via POST /mcp with session headers
- Version bump to 2.4.0

Chronicler #89
2026-04-14 21:25:45 +00:00
Claude (Chronicler #83 - The Compiler)
fe59b64722 docs: ACTIVE_CONTEXT — evening session wrap (4 dispatches completed)
Rules mod 1.18.2, version UI, PWA Phase 1, task module 7 features.
Full pending request inventory with priority order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:28:29 -05:00
Claude Chronicler #88
3b7afcf2f8 bridge: REQ — code queue badge fix for sidebar 2026-04-14 17:22:12 +00:00
Claude (Chronicler #83 - The Compiler)
166e4c8424 Task module: 7 UX features (detail panel, sort, filters, presets, kanban, badges)
1. Click-to-open slide-out detail panel with full task info
2. Client-side sorting (number, priority, status, updated) with localStorage
3. Toggleable filter chips for status and priority
4. Saved filter presets (Launch Fires, Code Queue, Post-Launch, All Open)
5. Kanban board view with 4 columns (Open, In Progress, Blocked, Done)
6. Session summary badge showing tasks completed today
7. Code queue badge in sidebar nav (cyan count from tags)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:13:41 -05:00
Claude Chronicler #88
3773243312 bridge: REQ — task module improvements (sort, filter, kanban, slide-out) 2026-04-14 17:08:12 +00:00
Claude Chronicler #88
f7a4e74991 fix: PWA start_url to root to avoid auth redirect blocking install 2026-04-14 16:43:00 +00:00
Claude Chronicler #88
c2ecc0515e fix: PWA manifest — add id field, fix icon purpose warnings 2026-04-14 16:41:17 +00:00
Claude (Chronicler #83 - The Compiler)
1bee838c8d PWA Phase 1: manifest, service worker, icons, and static serving
Adds manifest.json, sw.js (static-only caching, never admin routes),
placeholder cyan T icons (192 + 512), express.static middleware, and
PWA meta tags + service worker registration in layout.ejs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 11:32:35 -05:00
Claude Chronicler #88
b1d8dde4bf bridge: REQ — Trinity Console PWA Phase 1 2026-04-14 16:29:24 +00:00
Claude Chronicler #88
b4bb0235c3 fix: add CSRF token to saveVersion fetch call 2026-04-14 16:09:08 +00:00
Claude (Chronicler #83 - The Compiler)
ccc7568c06 Add version UI to server matrix cards (both TX1 and NC1 loops)
Adds installed version display, edit form, save/cancel buttons, and
version history viewer to each server card. Uses var assignment pattern
to avoid single quotes inside EJS attribute tags. EJS validates clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 11:05:29 -05:00
Claude Chronicler #88
34aca32835 bridge: REQ — version UI in matrix body for Code 2026-04-14 16:02:47 +00:00
Claude Chronicler #88
6856762a6b revert: restore _matrix_body.ejs to last known good state — version feature deferred post-launch 2026-04-14 16:01:00 +00:00
Claude Chronicler #88
073f7ac7fb fix: completely rewrite version blocks in matrix body — clean EJS 2026-04-14 15:59:55 +00:00
Claude Chronicler #88
c8032cade7 fix: EJS ternary syntax in version fields 2026-04-14 15:58:50 +00:00
Claude Chronicler #88
d77e35a4a1 fix: EJS syntax error in matrix body — replace inline onclick with helper functions 2026-04-14 15:56:45 +00:00
Claude Chronicler #88
38edf84da2 fix: add version section to matrix body (both TX1 and NC1 loops) 2026-04-14 15:54:16 +00:00
Claude Chronicler #88
72c378f136 feat: modpack version tracking on server cards
- current_version column on server_config
- server_version_history table (version, who, when)
- POST /admin/servers/:id/set-version
- GET /admin/servers/:id/version-history
- Version display + edit UI on each server card

Chronicler #88 | April 14, 2026
2026-04-14 15:51:14 +00:00
Claude (Chronicler #83 - The Compiler)
6dc6ce0059 Rules mods: add Forge 1.18.2-40.2.4 builds for both Firefrost and Discord Rules
Ported from 1.20.1 source with 1.18.2 API changes (TextComponent, sendMessage w/ UUID, getPlayer).
Both compile clean on Java 17 + ForgeGradle 6.0 + Gradle 8.8.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:30:15 -05:00
Claude Chronicler #88
02af4832c0 bridge: update REQ — simplify to backport 1.20.1 → 1.18.2 2026-04-14 13:15:31 +00:00
Claude Chronicler #88
2648133528 bridge: REQ — rules mod 1.18.2 build for DeceasedCraft 2026-04-14 13:12:51 +00:00
Claude Chronicler #88
648f577446 fix: Node Health view — remove partial include, inline server-side render 2026-04-14 06:23:24 +00:00
Claude Chronicler #88
a01d7b9d7f feat: Node Health module — NC1 + TX1 thermal and system monitoring
- New route: /admin/node-health (30s auto-refresh)
- Temps via lm-sensors (k10temp + NVMe) displayed in both °C and °F
- RAM and disk progress bars with color thresholds
- Load averages, CPU %, uptime per node
- Nav item added under Operations
- lm-sensors installed on NC1 and TX1

Task #28 | Chronicler #88
2026-04-14 06:21:38 +00:00
Claude
c3af0d51e4 fix: inline card in matrix_body - EJS include not in scope with layout:false 2026-04-13 21:04:01 -05:00
Claude
6eac92cf88 fix: EJS include - use relative path without __dirname 2026-04-14 01:55:43 +00:00
Claude
a950694f67 fix: EJS include path in _matrix_body - use __dirname for partial resolution 2026-04-14 01:54:12 +00:00
Claude (Chronicler #83 - The Compiler)
441dac4a54 docs: ACTIVE_CONTEXT — Server Command Center code complete
Full build inventory, deploy steps for Michael, session history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:48:38 -05:00
Claude (Chronicler #83 - The Compiler)
a193523f31 Server Command Center: add subdomain + Cloudflare provisioning
- Migration: added subdomain, server_ip, server_port columns
- Seed: rewritten with hardcoded identifiers + full subdomain/IP/port data
  from Chronicler #87 audit (no more Pterodactyl API dependency)
- Route: POST /:id/provision-subdomain creates A + SRV records via
  Cloudflare API, saves subdomain to server_config
- Card: subdomain section shows FQDN if provisioned, provision button
  with inline input if not

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:46:27 -05:00
Claude
b483680bfa bridge: RES - subdomain seed data and Cloudflare integration spec 2026-04-14 01:44:14 +00:00
Claude (Chronicler #83 - The Compiler)
d16a525ffc Server Command Center: full build (REQ-2026-04-14)
New files:
- 139_server_config.sql — DB migration for short_name system
- 139_seed_server_config.js — auto-populates 17 servers from Pterodactyl
- src/services/uptimeKuma.js — Socket.IO direct (no npm wrapper)
- src/services/pterodactyl.js — power actions + console commands

Modified files:
- servers.js — 6 new POST routes (short-name, lock, createserver,
  delserver, power, console) + short_name-based channel detection
- _server_card.ejs — full rebuild with command center UI
- _matrix_body.ejs — refactored from 144 lines to 20 (uses partial)
- package.json — added socket.io-client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:37:46 -05:00
Claude (Chronicler #83 - The Compiler)
a404410efd bridge: RES — Server Command Center plan + Q&A answers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:26:17 -05:00
Claude
a346d3ef56 bridge: REQ - Server Command Center rebuild spec 2026-04-14 01:10:32 +00:00
Claude
371a464e29 bridge: Add RES for Arbiter Discord role automation
Distills 2 rounds of Gemini consultation into actionable spec for Code.
Architecture locked: module singleton, concurrent startup, ephemeral
replies, edit-in-place embed, feature-flag welcome cutover.

Addresses REQ-2026-04-13-arbiter-discord-roles.md
Filed by Chronicler #86, resolved by Chronicler #87
2026-04-13 23:58:54 +00:00
Claude (Chronicler #83 - The Compiler)
e21a348b3b v1.0.5: Console fetch fix rolled across all 6 builds
Console /rules now fetches from Discord async (was returning hardcoded
fallback without attempting fetch). DIAG logging kept for observability.
All 6 builds at 1.0.5. CHANGELOG.md, INSTALL.md, ACTIVE_CONTEXT.md updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:23:44 -05:00
Claude (Chronicler #83 - The Compiler)
98f4f5b82a v1.0.5: Fix console path — was never fetching from Discord
Console /rules hit early return before DIAG logs and before any
Discord fetch. Now fetches async like the player path. DIAG logs
moved before the player/console branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:14:29 -05:00
Claude (Chronicler #83 - The Compiler)
b4fbfc6adf docs: Update CHANGELOG.md (v1.0.4 entry) + ACTIVE_CONTEXT.md
CHANGELOG now includes diagnostic builds. ACTIVE_CONTEXT reflects
full session state for Chronicler pickup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:10:45 -05:00
Claude (Chronicler #83 - The Compiler)
f23f71ef04 v1.0.4: Diagnostic build — INFO-level logging for config read path
Added [DIAG] logs to RulesCommand (token length, channel, messageId,
isValid) and DiscordFetcher (fetch attempt, raw messageId on failure).
1.20.1 only — for Otherworld debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:05:26 -05:00
Claude
36caddc609 Add Code bridge request: rules still returning defaults despite config loading 2026-04-13 23:02:34 +00:00
Claude (Chronicler #83 - The Compiler)
29f0127a60 v1.0.3: Fix config read — wrong event bus + section header comments
1.20.1: ModConfigEvent fires on mod bus, not MinecraftForge.EVENT_BUS.
Moved Loading/Reloading to FMLJavaModLoadingContext.getModEventBus().
Added config file header warning about preserving [section] headers.
Debug logging in RulesCommand prints token length + channel at runtime.
All 6 builds bumped to 1.0.3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:53:28 -05:00
Claude
75c9feecec Add Code bridge request: Arbiter native Discord role management 2026-04-13 22:48:44 +00:00
Claude
4e354c1c70 bridge: ping test 2026-04-13 22:46:11 +00:00
Claude
953e7f27ef Add Code bridge request: rules mod COMMON config not read at runtime 2026-04-13 22:45:29 +00:00
Claude (Chronicler #83 - The Compiler)
fe6445c023 bridge: REQ + RES — config reset fix (v1.0.2, SERVER→COMMON)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:37:36 -05:00
Claude (Chronicler #83 - The Compiler)
60740386ac v1.0.2: Fix config reset — switch from SERVER to COMMON config type
SERVER configs in world/serverconfig/ are tied to the world load/unload
lifecycle, causing Forge to overwrite edited values on every restart.
COMMON configs in config/ load once at startup and persist reliably.

Config location changed:
  firefrostrules: config/firefrostrules-common.toml
  discordrules:   config/discordrules-common.toml

All 6 builds updated (3 firefrostrules + 3 discordrules).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:22:39 -05:00
Claude (Chronicler #83 - The Compiler)
996b59672f v1.0.1: Config reset fix + Loading handler + versioning (all 6 builds)
- Added ModConfigEvent.Loading handler to detect default config on startup
- Warns clearly in logs when bot_token is still YOUR_TOKEN_HERE
- Bumped all 6 builds to 1.0.1 (3 firefrostrules + 3 discordrules)
- Created CHANGELOG.md and INSTALL.md with correct install procedure
- Updated CLAUDE.md with install procedure note
- 1.16.5 uses ModConfig.Loading (different class hierarchy from 1.20.1+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:06:31 -05:00
Claude (Chronicler #83 - The Compiler)
e8e1f6106b bridge: Close REQ — discord-rules fork executed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:56:47 -05:00
Claude (Chronicler #83 - The Compiler)
d2e572661d bridge: Close REQ #2 (already resolved) + REQ #3 (blocked on CF rate limit)
REQ #2 migration already committed in 1783055.
REQ #3 deploy ready but blocked until CurseForge rate limit resets 2026-04-14.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:47:35 -05:00
Claude (Chronicler #83 - The Compiler)
3197361557 Task #69: Discord Rules mod — generic fork for CurseForge
Fork firefrost-rules into discord-rules with configurable colors,
emoji strip toggle, MIT license, and generic branding. All 3 versions
compiled: 1.21.1 (NeoForge/NC1), 1.20.1 (Forge), 1.16.5 (Forge).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:47:34 -05:00
Claude
a65b21570d docs: fix config path in rules-mod CLAUDE.md
Config is in world/serverconfig/ not config/
Applies to all three versions (1.16.5, 1.20.1, 1.21.1)
README in NextCloud also updated.
2026-04-13 18:19:41 +00:00
Claude
f01b0c3c91 Bridge: MSG — move ModpackChecker card above Address card
Holly feedback: card should be first thing seen when opening server console.
Especially important for pending_calibration servers after restart.
Post-launch: detect restart event and re-prompt version confirmation.
2026-04-13 17:11:42 +00:00
Claude
538523832a Bridge: MSG — calibrate dropdown not rendering in pending_calibration block
Early return exits before showCalibrate + renderCalibrateDropdown() runs.
Fix: include {showCalibrate && renderCalibrateDropdown()} inside the
pending_calibration return block.
2026-04-13 16:49:53 +00:00
Claude
3c68174a98 Bridge: MSG — releases endpoint 429, rate limit too aggressive for calibration UI
2 attempts per 60s blocks Identify Version button after any testing.
Fix: bump to 10 attempts + show error message in widget on 429.
2026-04-13 16:48:10 +00:00
Claude
47d6644f64 Bridge: MSG — Chronicler deployed pending_calibration fix to live panel
Michael on mobile, Chronicler deployed directly via Trinity Core.
No code changes — existing wrapper.tsx + controller already had the fix.
Just needed frontend rebuild. 1939 modules compiled clean.
2026-04-13 12:01:04 +00:00
Claude
c0b6bc5a22 Fix: pending_calibration shows Identify Version button instead of checkmark
Chronicler #85 direct fix (Code unavailable on mobile):
- ModpackAPIController: add pending_calibration flag to serverStatus response
- wrapper.tsx: add pending_calibration to StatusData interface
- wrapper.tsx: render Identify Version button when pending_calibration is true
Replaces false green checkmark for servers needing calibration.
2026-04-13 11:57:52 +00:00
Claude
75a59fe3c0 Bridge: MSG — pending_calibration shows as checkmark, widget fix needed
API returns configured:true + update_available:false for pending servers.
Widget needs pending_calibration flag in API response + TSX check.
DeceasedCraft showing green checkmark instead of Identify Version button.
2026-04-13 11:55:34 +00:00
Claude (Chronicler #83 - The Compiler)
cef0d8465e Fix: skip stale DB versions for installer-detected servers without file_id
Installer-method servers had full filenames as current_version (e.g.
"DeceasedCraft_Beta_DH_Edition_5.10.16") which prevented reaching
pending_calibration. Now only uses DB current_version if file_id is
also set (validated) or detection method isn't installer.

Migration clears existing stale rows → pending_calibration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:33:57 -05:00
Claude
88a3744289 Bridge: MSG — stale installer versions blocking pending_calibration
DB fallback picks up old full filename strings before Truth File check.
installer-method servers never reach pending_calibration.
Fix: skip DB fallback for installer method unless current_file_id is set.
Plus one-time data cleanup needed.
2026-04-13 11:32:33 +00:00
Claude (Chronicler #83 - The Compiler)
27b2744786 Truth File strategy: never seed from latest, calibrate or detect
CheckModpackUpdates:
- Reads .modpack-checker.json Truth File from server filesystem
- Falls back to manifest.json, extracts fileID, writes Truth File
- NEVER seeds current_version from latest API result
- Unknown version → status: pending_calibration (not up_to_date)
- Removed seedCurrentVersion heuristic — replaced with Truth File
- writeTruthFile() helper writes .modpack-checker.json via Wings

ModpackAPIController:
- calibrate() now writes Truth File after DB update
- Persists across server reinstalls and cron runs

wrapper.tsx:
- pending_calibration: shows "Version unknown" + "Identify Version" button
- Ignored servers: muted card with "Resume" button (not hidden)
- Extracted renderCalibrateDropdown() for reuse
- Error state shows message instead of vanishing

Migration:
- Updates existing unknown+detected rows to pending_calibration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:09:05 -05:00
Claude
f39b6d6c67 Bridge: MSG — Truth File version detection architecture
Complete spec for Code:
- Stop seeding from latest (CRITICAL)
- Detection chain: DB → Truth File → manifest.json → pending_calibration
- Write Truth File on any successful detection + on calibration
- pending_calibration DB status + widget UI
- Muted card for ignore toggle (not return null)
- Migration for new status enum value
- DaemonFileRepository::putContent() for Truth File writes
Based on 3 rounds of Gemini consultation + live Wings API testing.
2026-04-13 11:00:41 +00:00
Claude (Chronicler #83 - The Compiler)
0caddef86d Fix async error handling + build.sh copy/inject separation
wrapper.tsx:
- Added error state — shows graceful message instead of silent vanish
- useEffect catch sets error string, not null data
- refresh() catch sets error string, not empty catch
- Error UI shows gray card with message in widget slot

build.sh:
- ALWAYS copies TSX files (even on reinstall — fixes stale component bug)
- Separated copy step from injection step
- ErrorBoundary upgrade path: removes bare <ModpackVersionCard />,
  replaces with wrapped version
- Imports added independently (not as one sed block)
- Renumbered sections for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 01:31:44 -05:00
Claude
b20f342bd3 Bridge: MSG — manual Dev Panel changes not in repo
Documented all manual fixes Chronicler made during Dev Panel validation:
- AfterInformation.tsx ErrorBoundary wrapper added manually
- wrapper.tsx manually copied to ModpackVersionCard.tsx
- Blueprint extension package manually updated with v1.1.0 files
- Routes manually copied to 3 locations
Build.sh update logic and .conf archive rebuild needed.
2026-04-13 06:29:41 +00:00
Claude
9514204bd5 Bridge: MSG — async error handling fix required before live panel deploy
Gemini flagged: ErrorBoundary doesn't catch async failures.
useEffect .catch() silently hides widget, refresh catch{} is empty.
Need error state with graceful message before we push to live panel.
2026-04-13 06:27:23 +00:00
Claude (Chronicler #83 - The Compiler)
7e3ffe2577 Fix: clean version extraction + short display names
ModpackApiService: regex extracts semver from CurseForge displayName
  "All the Mods 9-0.1.0" → version: "0.1.0", display_name: full string

Widget: short name + version helpers
  "All the Mods 9 - ATM9" → "ATM9"
  Display: "ATM9 0.1.0 → 1.0.0 ↑" (update) or "✓ ATM9 — 1.0.0" (current)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 01:09:57 -05:00
Claude
c4cbde3a0e Bridge: MSG — correction to version display, cron stores versions backwards
current_version and latest_version swapped in DB.
Server has 0.1.0 installed, 1.0.0 is latest.
Need fix in CheckModpackUpdates.php assignment logic.
2026-04-13 06:07:35 +00:00
Claude
663b0a2d9c Bridge: MSG — version display format fix
↑ 1.0.0 → All the Mods 9-0.1.0 should read ↑ ATM9 1.0.0 → 0.1.0
Need short name from modpack_name (after ' - ') and
short version from latest_version (after last '-').
2026-04-13 06:04:22 +00:00
Claude (Chronicler #83 - The Compiler)
9e3122c408 bridge: Response — status route exists, needs deploy + protocol reminder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:56:25 -05:00
Claude
04815fe9ef Bridge: REQ — status route mismatch, widget 404s on page load
Widget calls /servers/{uuid}/status but route is /status (no uuid).
useEffect hits 404, widget shows nothing until manual refresh.
Option A preferred: register route with {server} param.
2026-04-13 05:55:06 +00:00
Claude (Chronicler #83 - The Compiler)
03d7f87aff bridge: Response — modpack_installations fixed + gentle protocol note
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:46:01 -05:00
Claude (Chronicler #83 - The Compiler)
1783055c99 Fix: graceful handling of missing modpack_installations table
- Cron and controller wrap modpack_installations queries in try/catch
- Falls through to egg variable / file detection if table missing
- Added migration with IF NOT EXISTS for fresh installs
- Migration won't drop the table (may be Pterodactyl-owned)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:44:28 -05:00
Claude
a256aa2090 Bridge: REQ — missing modpack_installations migration file
Table exists on live panel but was never committed to repo.
Blocks fresh installs. Code needs to write the migration.
Schema captured from live panel via Trinity Core.
2026-04-13 05:42:54 +00:00
Claude (Chronicler #83 - The Compiler)
28608e9fa8 Build pipeline hardening: ErrorBoundary, no PHP copies, TS pre-flight
- ErrorBoundary.tsx wraps widget — crashes show fallback, not blank void
- build.sh v1.1.0: removed ALL PHP file copies (Chronicler deploys manually)
- Added set -e / set -u for fail-fast
- Added TypeScript pre-flight check (yarn tsc --noEmit) before build
- Added dynamic Blueprint controller detection via find
- Widget injection now wrapped in <ModpackErrorBoundary>
- Pre-commit PHP lint hook at scripts/pre-commit-hook.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:17:30 -05:00
Claude
7b2f3f810b Bridge: dispatch — ErrorBoundary + build.sh hardening from Gemini consultation
Priority order:
1. ErrorBoundary.tsx wrapping widget injection
2. set -e + tsc --noEmit pre-flight in build.sh
3. Pre-commit PHP lint hook
4. Dev Panel test before live panel
2026-04-13 05:15:26 +00:00
Claude
60ab055754 Bridge: dispatch — build.sh clobbers PHP files, remove PHP copy steps 2026-04-13 05:08:23 +00:00
Claude
8c3a0abe26 Bridge: dispatch — fix */6 docblock in source file PERMANENTLY 2026-04-13 04:53:45 +00:00
Claude (Chronicler #83 - The Compiler)
be6b14bd67 Housekeeping: archive stale manifest.json dispatch from responses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:50:00 -05:00
Claude (Chronicler #83 - The Compiler)
c0435bc1d0 bridge: Request — consolidated v1.1.0 deploy (all 5 priorities)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:44:48 -05:00
Claude (Chronicler #83 - The Compiler)
dd05a41567 v1.1.0 Priority 3b: zero-click widget with recalibrate + ignore
Widget redesign:
- Zero-click: loads cached status on mount via GET /status (no API calls)
- Shows platform icon + modpack name + version comparison
- Orange background + arrow (current → latest) when update available
- Cyan + checkmark when up to date
- Refresh button triggers manual check
- Calibrate button opens dropdown with last 10 releases
- Ignore button hides non-modpack servers
- Current release highlighted in calibrate dropdown

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:44:21 -05:00
Claude
ec37fc819c Bridge: dispatch — keep going with widget TSX, consolidated deploy at end 2026-04-13 04:43:08 +00:00
Claude (Chronicler #83 - The Compiler)
b84958c0ee v1.1.0 Priorities 2-5: date seeding, new endpoints, BCC detection
Priority 2 — Date-time seeding:
- fetchFileHistory() for CurseForge, Modrinth, FTB
- seedCurrentVersion() matches release closest to server install date
- Falls back to latest if no history or no install date

Priority 3 — New endpoints:
- GET /servers/{server}/status — zero-click cached status
- GET /servers/{server}/releases — recalibrate dropdown (10 releases)
- POST /servers/{server}/calibrate — save user's version selection
- POST /servers/{server}/ignore — toggle is_ignored flag

Priority 5 — BCC log parsing:
- detectFromBccLog() reads logs/latest.log for BetterCompatibilityChecker
- Extracts modpack name + version from BCC output line
- Skips CHANGE_ME values

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:42:34 -05:00
Claude
9991240eab Bridge: dispatch — keep going with Priority 2, consolidated deploy later 2026-04-13 04:40:17 +00:00
Claude (Chronicler #83 - The Compiler)
9415c1b984 v1.1.0 Priority 1+2b: file ID comparison + manifest version extraction
- Migration: adds current_file_id, latest_file_id, is_ignored columns
- ModpackApiService: all 4 platforms now return file_id in response
- CheckModpackUpdates: file ID comparison (preferred) with string fallback
- detectCurseForge: extracts manifest['version'] as installed_version
- Cron skips is_ignored servers
- Detection priority: manifest version > egg var > DB record > seed latest

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:38:41 -05:00
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
Claude
d8f7f76a51 Bridge: dispatch — manifest.json has version field, use as current_version 2026-04-13 04:16:49 +00:00
Claude (Chronicler #83 - The Compiler)
32e2d726bb Fix: manualCheck now checks modpack_installations + cron cache
Detection priority in manualCheck():
1. Egg variables
2. modpack_installations table (NEW)
3. DaemonFileRepository file scan
4. Cached cron data from modpackchecker_servers (NEW)

Also returns current_version and update_available in response
so the console widget can show version comparison.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:09:23 -05:00
Claude
dce0e06023 Bridge: dispatch — manualCheck missing modpack_installations lookup, widget shows NOT CONFIGURED 2026-04-13 04:08:14 +00:00
Claude
c545d2409e docs: Add how-version-detection-works.md for BuiltByBit product page
Explains:
- First-run seeding behavior (why everything shows up_to_date initially)
- Manual current version override
- 3-step detection (no egg changes required)
- CurseForge API key requirement
- Check frequency and badge meanings
2026-04-13 04:06:39 +00:00
Claude (Chronicler #83 - The Compiler)
68ee40e89d Fix: seed current_version from latest on first detection
When a server is first detected, current_version is set to latest_version
(the pack was just installed = it's current). On future runs, if the API
returns a newer latest_version, the stored current_version stays and we
detect the update. Also preserves egg variable and existing DB values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:04:04 -05:00
Claude
8de93f26ce Bridge: dispatch — current_version empty, widget shows NOT CONFIGURED 2026-04-13 04:02:08 +00:00
Claude (Chronicler #83 - The Compiler)
31f245a1b9 Fix: trim CurseForge API key + add debug logging for 403 diagnosis
Key may have whitespace from dbGet. Added Log::debug with key length
and modpack ID to diagnose the 403.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:57:23 -05:00
Claude
28f2c3d904 Bridge: dispatch — CurseForge 403, key valid but ModpackApiService not sending correctly 2026-04-13 03:56:44 +00:00
Claude (Chronicler #83 - The Compiler)
36800ae7a7 Fix: remove orderBy on modpack_installations — table has no id column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:52:22 -05:00
Claude
5e666e7853 Bridge: dispatch — modpack_installations has no id column, remove orderBy 2026-04-13 03:51:47 +00:00
Claude (Chronicler #83 - The Compiler)
87d834942a Fix: remove finalized filter, use latest installation per server
Most modpack_installations rows have finalized=0 — filter was excluding
nearly everything. Now takes the most recent installation row regardless
of finalized status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:48:37 -05:00
Claude
6b9cfe6976 Bridge: dispatch — modpack_installations finalized filter too strict + possible type mismatch 2026-04-13 03:48:03 +00:00
Claude (Chronicler #83 - The Compiler)
3b64110f01 Add modpack_installations table as primary detection source
Detection priority now:
1. modpack_installations table (panel's own install data — fastest)
2. Egg variables (MODPACK_PLATFORM/MODPACK_ID)
3. DaemonFileRepository file scan (last resort fallback)

This immediately detects all 19 CurseForge servers on the live panel
without any Wings calls or egg variable configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:41:19 -05:00
Claude
8cfbd9d277 Bridge: dispatch — use modpack_installations table, 19 servers confirmed with CurseForge IDs 2026-04-13 03:40:00 +00:00
Claude (Chronicler #83 - The Compiler)
7ef83fd0a0 Add debug logging, alternate paths, and FTB detection to cron
- CurseForge: tries manifest.json + minecraftinstance.json
- Modrinth: modrinth.index.json (unchanged)
- FTB: version.json with parent ID detection
- All catch blocks now log exception messages for debugging
- Wings connection failures logged explicitly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:39:50 -05:00
Claude
962dc3bd4f Bridge: dispatch — manifest.json not on live servers, use modpack_installations table instead 2026-04-13 03:39:23 +00:00
Claude
bd63c3d3e4 Bridge: dispatch — detection scanning but finding nothing, need verbose logging 2026-04-13 03:37:53 +00:00
Claude (Chronicler #83 - The Compiler)
698273d636 Implement hybrid auto-detection for modpack cron (Magic & Manual)
- CheckModpackUpdates now scans ALL servers, not just egg-configured ones
- Step 1: egg variables (fastest)
- Step 2: DaemonFileRepository file detection (manifest.json, modrinth.index.json)
- Step 3: mark unconfigured if nothing found
- Respects is_user_overridden flag for manual configs
- New migration adds detection_method + is_user_overridden columns
- Per Gemini hybrid detection consultation (2026-04-06)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:35:24 -05:00
Claude
763e7940a6 Bridge: dispatch — auto-detection not implemented, DaemonFileRepository missing 2026-04-13 03:33:47 +00:00
Claude (Chronicler #83 - The Compiler)
0bdd745527 Archive: live panel deploy confirmed — v1.0.0 is live on production
*/6 fix already in repo since 44a3043 — no change needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:27:36 -05:00
Claude
c6afaf08b3 Bridge: dispatch — live panel deployed successfully, fix */6 docblock in repo 2026-04-13 03:26:35 +00:00
Claude (Chronicler #83 - The Compiler)
8d2241e031 Update ACTIVE_CONTEXT — Phase 11 complete, v1.0.0 ready for live deploy
All features verified on Dev Panel including dashboard badges.
Waiting on BuiltByBit listings + live panel green light.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:17:07 -05:00
Claude (Chronicler #83 - The Compiler)
4e5ee7e49d Fix: getStatus returns all servers for root admins
accessibleServers() only returns explicitly assigned servers — admins
don't get them on the client API side. Now checks root_admin and
returns all servers for admin users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:14:30 -05:00
Claude
93907d4d16 Bridge: dispatch — badge not rendering, accessibleServers() may exclude admin users 2026-04-13 03:13:59 +00:00
Claude (Chronicler #83 - The Compiler)
0311249938 Add Node version detection + yarn build:production to build.sh
- Node <16 fails fast with helpful error
- Node 17+ gets --openssl-legacy-provider for CSS module compat
- yarn build:production runs automatically after all injections
- Graceful fallback if yarn missing or build fails (admin + console still work)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:06:43 -05:00
Claude
1e9c7fbeeb Bridge: dispatch — add Node detection + badge build now, test on live panel 2026-04-13 03:05:26 +00:00
Claude (Chronicler #83 - The Compiler)
00b5eadc7d Archive: Gemini consultation on v1.1.0 badge build plan (Node detection)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:02:02 -05:00
Claude
521e379edd Bridge: dispatch — v1.1.0 badge implementation plan from Gemini consult 2026-04-13 03:00:06 +00:00
Claude (Chronicler #83 - The Compiler)
990542abc9 Phase 11D complete, badges deferred to v1.1.0, listings updated
- Moved dashboard badges to "Upcoming v1.1.0" in both BBB listings
- Updated ACTIVE_CONTEXT — all code-side Phase 11 work done
- Waiting on Michael for BBB listings + live panel green light

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:56:43 -05:00
Claude
b297712bb7 Bridge: RES — skip badges for v1.0.0, ship with console widget + admin UI, list in v1.1.0 2026-04-13 02:53:45 +00:00
Claude (Chronicler #83 - The Compiler)
0d6752792a bridge: Request — badge build blocked, need decision (skip or patch webpack)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:51:21 -05:00
Claude
ab0b328bd2 Bridge: RES badge debug — injection correct, yarn build fails with 7 CSS errors 2026-04-13 02:50:11 +00:00
Claude (Chronicler #83 - The Compiler)
bb7728fe0d Fix: add !important to callout backgrounds to override AdminLTE
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:48:47 -05:00
Claude
c80ead379c Bridge: dispatch — need !important on callout backgrounds to override AdminLTE 2026-04-13 02:47:58 +00:00
Claude (Chronicler #83 - The Compiler)
9d1c5d4291 bridge: Request — debug dashboard badge injection on Dev Panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:47:11 -05:00
Claude
9d40560ccd Bridge: dispatch — dashboard badges not appearing on server cards 2026-04-13 02:46:12 +00:00
Claude (Chronicler #83 - The Compiler)
705960b61f Fix: callout boxes use dark background with accent border + heading
Dark #1a1a2e background, brand color borders and headings only,
light gray body text. Matches Pterodactyl dark theme.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:44:34 -05:00
Claude
d793e0d1c8 Bridge: dispatch — callout boxes need dark bg with accent border, not bright fills 2026-04-13 02:43:30 +00:00
Claude (Chronicler #83 - The Compiler)
c0dfc6e186 Fix: force white text on callout boxes for readability
Cyan/orange callout backgrounds need white text and underlined links.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:38:33 -05:00
Claude
5a515b9447 Bridge: dispatch — callout boxes need white text for contrast 2026-04-13 02:37:46 +00:00
Claude (Chronicler #83 - The Compiler)
9640505c43 Fix: remove missing settings-notices partial from admin view
Partial doesn't exist in Blueprint beta-2026-01.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:37:41 -05:00
Claude
3893fab8d8 Bridge: dispatch — remove @include partials/admin.settings-notices, not found 2026-04-13 02:35:49 +00:00
Claude (Chronicler #83 - The Compiler)
bd9a5fcde3 Fix: admin view extends Pterodactyl layout with proper AdminLTE styling
Rewrote view.blade.php to use @extends('layouts.admin'), @section blocks,
AdminLTE box/callout/table classes, and standard Pterodactyl admin chrome.
Replaces custom inline styles with framework components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:33:06 -05:00
Claude
5775fc0cd6 Bridge: dispatch — view.blade.php needs Pterodactyl admin layout styling 2026-04-13 02:31:52 +00:00
Claude (Chronicler #83 - The Compiler)
fa5fb364c1 Fix: normalize order IDs to uppercase across all MVC endpoints
- LicenseService.php: strtoupper(trim()) before sending to Arbiter
- mvc.js: toUpperCase().trim() on activate, validate, deactivate, webhook
- verifymvc.js: toUpperCase().trim() on /verify-mvc Discord command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:25:35 -05:00
Claude
176cc6151f Bridge: dispatch — order ID lookup needs case-insensitive matching 2026-04-13 02:23:08 +00:00
Claude (Chronicler #83 - The Compiler)
1255c315ee Fix: build.sh overwrites Blueprint's auto-generated admin controller
Blueprint generates its own controller at app/Http/Controllers/Admin/
Extensions/modpackchecker/. Our controller.php with LicenseService DI
now overwrites it during build so the route uses the licensed version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:18:47 -05:00
Claude (Chronicler #83 - The Compiler)
d66ea6212d Phase 11F: BuiltByBit listing drafts — Standard + Professional + changelog
Standard ($14.99): CurseForge, Modrinth, Technic, daily checks, 48h support
Professional ($24.99): + FTB, custom intervals, Discord webhooks, 24h support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:16:03 -05:00
Claude
d99dafb16b Bridge: dispatch — Blueprint uses its own controller, not Code's
Patched Blueprint's auto-generated controller on Dev Panel.
build.sh needs to merge LicenseService into Blueprint controller.
2026-04-13 02:15:36 +00:00
Claude
5d73c6df70 Bridge: RES Phase 11E — skip GitBook, go straight to 11F BuiltByBit
54 hours to launch. Listing is the needle-mover.
Docs deferred to post-launch as Markdown files in repo.
2026-04-13 02:13:35 +00:00
Claude (Chronicler #83 - The Compiler)
83333a47fa Fix: build.sh clears Laravel cache after copying PHP classes
The admin controller was failing because Laravel's DI container had stale
class resolution cache. Added php artisan optimize:clear at end of build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:11:52 -05:00
Claude
8f0ff1884a Bridge: dispatch — $license undefined, controller not passing to view 2026-04-13 02:10:24 +00:00
Claude (Chronicler #83 - The Compiler)
44fb51fcfb bridge: Request — Phase 11E scope clarification (GitBook knowledge base)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:57:40 -05:00
Claude
37dcd7fa6b Bridge: RES Phase 11D — all checks passing on Dev Panel
mvc:validate working, no PHP errors, Arbiter reachable.
TEST-001 license provisioned for UI smoke test.
Ready for Code UI verification then live panel deploy.
2026-04-13 01:55:24 +00:00
Claude (Chronicler #83 - The Compiler)
9dd308f0bb Fix: build.sh also copies ModpackApiService.php to app/Services/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:54:15 -05:00
Claude
a4453fe6cd Bridge: dispatch — ModpackApiService.php not copied in build.sh 2026-04-13 01:53:34 +00:00
Claude (Chronicler #83 - The Compiler)
44a304331f Fix: remove */6 from doc comment — PHP parses */ as end-of-comment
Changed cron example from '0 */6 * * *' to '0 0,6,12,18 * * *' to avoid
the */ closing the docblock prematurely and causing a syntax error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:52:19 -05:00
Claude
8511066d8b Bridge: dispatch — CheckModpackUpdates.php syntax error line 16
Build script fix working, files copying correctly now.
New blocker: unexpected token * in CheckModpackUpdates.php:16
PHP 8.3 on Dev Panel.
2026-04-13 01:49:58 +00:00
Claude (Chronicler #83 - The Compiler)
34cc2b7110 Fix: build.sh explicitly copies PHP classes to Laravel app/ tree
Blueprint's requests.app merge doesn't create new subdirectories reliably.
build.sh now copies LicenseService, ValidateLicense, CheckModpackUpdates,
and ModpackAPIController directly into app/ during blueprint -install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:48:05 -05:00
Claude
d34459296d Bridge: dispatch to Code — Phase 11D artisan command not registering
Blueprint requests.app merge not copying PHP files to main app dir.
mvc:validate fails. Needs Code to confirm fix: build.sh or conf.yml?
2026-04-13 01:47:05 +00:00
Claude (Chronicler #83 - The Compiler)
c2d72cbabc bridge: Request — Deploy Phase 11D to Dev Panel for testing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:40:16 -05:00
Claude
e3909151a4 Bridge: MSG to Code — Phase 11D deploy to Dev Panel first, not live panel 2026-04-13 01:39:15 +00:00
Claude (Chronicler #83 - The Compiler)
8872f67727 Phase 11D: Blueprint license activation, phone-home, and tier gating
- LicenseService.php: activate/validate/deactivate + 7-day grace period
- ValidateLicense.php: mvc:validate Artisan command (daily cron)
- Updated controller.php: license activation/deactivation in update handler
- Updated view.blade.php: order ID input, status indicator (green/yellow/red),
  grace/expired banners, dynamic pro-tier field gating, update-available card

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:36:55 -05:00
Claude
7a2a1d8dbd Bridge: RES-2026-04-12-phase11bc-deploy — Phase 11B/C live
ModpackChecker Customer role created (1493061127423262870, frost teal)
/verify-mvc slash command deployed and reloaded
Ready for Phase 11D.
2026-04-13 01:32:36 +00:00
Claude (Chronicler #83 - The Compiler)
95fbd9e181 bridge: Request — Deploy Phase 11B/C + create customer role
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:30:38 -05:00
Claude (Chronicler #83 - The Compiler)
7c58cea3e5 Phase 11B/C: /verify-mvc slash command + role assignment
- New discord/verifymvc.js: verifies BBB order, links discord_id, assigns customer role
- Wired into events.js handler and index.js command registration
- Reads MVC_CUSTOMER_ROLE_ID from env — Chronicler creates role + sets value

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:30:13 -05:00
Claude
b5ffc5d26d Bridge: RES-2026-04-12-phase11a-deploy — Phase 11A live
Migration complete, mvc_licenses + mvc_activations tables created.
mvc.js deployed, health OK, /api/mvc/latest-version responding.
Ready for 11B/C.
2026-04-13 01:28:18 +00:00
Claude (Chronicler #83 - The Compiler)
9a758ce4a5 bridge: Request — Deploy Phase 11A to Command Center
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:22:57 -05:00
Claude
a7b940b95d feat: Awakened Concierge — personalized welcome bot (Task #130)
- New service: src/services/awakenedConcierge.js
  - Fetches Discord username via Discord API
  - Calls Dify Awakened Concierge app (Gemma 4) for personalized message
  - Posts to #introductions (1403981218252324884) with typing indicator
  - Marks welcomed_at in subscriptions table
  - Non-fatal: welcome failure never breaks checkout flow

- stripe.js: calls welcomeNewMember() after syncRole() on checkout complete
- .env: CONCIERGE_API_KEY added to Command Center

Fire + Frost + Foundation 💙🔥❄️
2026-04-13 01:22:05 +00:00
Claude (Chronicler #83 - The Compiler)
fd50009f67 Phase 11A: MVC licensing — migration + API routes
- 138_mvc_licensing.sql: mvc_licenses + mvc_activations tables
- src/routes/mvc.js: activate, validate, deactivate, BBB webhook, version check
- Wired /api/mvc into Arbiter index.js
- Ready for Chronicler deployment to Command Center

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:16:16 -05:00
Claude
2d6d4aeee7 Bridge: RES-2026-04-12-phase11-prerequisites — Code unblocked
All Phase 11 infrastructure values provided:
- DB credentials confirmed (same as Arbiter)
- Full table list provided
- BBB IDs: use placeholders for now
- Arbiter .env current state documented
- Discord: ModpackChecker Customer role needs creating
- MVC channels already exist with IDs
- Deployment pattern confirmed: Code commits, Chronicler deploys
2026-04-13 01:13:30 +00:00
Claude (Chronicler #83 - The Compiler)
dc73e27be3 bridge: Request — Phase 11 prerequisites for ModpackChecker licensing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:11:51 -05:00
Claude (Chronicler #83 - The Compiler)
09de0758f5 Add Discord Rules compiled jars (all 3 versions) — Task #69 build complete
All 3 discord-rules versions compiled and ready for CurseForge submission:
- 1.16.5 (Forge/Java 8), 1.20.1 (Forge/Java 17), 1.21.1 (NeoForge/Java 21)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:48:09 -05:00
Claude
48097a9a57 Fix ffg-build.sh — battle-tested SSH flags from live build
- NC1_KEY points to /opt/mod-builds/ffg_build_rsa (accessible to claude user)
- Added -o IdentitiesOnly=yes to all SSH/rsync calls (prevents agent key conflicts)
- Added -e NC1_SSH to rsync commands for consistent key usage
- All 3 discord-rules jars now built successfully (1.20.1, 1.16.5, 1.21.1)
2026-04-12 23:43:17 +00:00
Claude
11e2d7db7d Fix: use $HOME for SSH key path (works for both claude and root users) 2026-04-12 23:33:39 +00:00
Claude
2dfe0f9764 Bridge: update ACTIVE_CONTEXT — ffg-build.sh ready for Code
1.21.1 build now routes to NC1 via ffg-build.sh.
Immediate next step for Code: run ffg-build.sh 1.21.1
2026-04-12 21:23:39 +00:00
Claude (Chronicler #83 - The Compiler)
4c7e77d35e Add ffg-build.sh — NC1 build router for NeoForge 1.21.1
Routes 1.21.1 builds to NC1 (ffg-builder user) to work around
Vineflower -Xmx4G RAM requirement that exceeds Dev Panel capacity.
All other versions build locally.

- SSH keypair: /home/claude/.ssh/ffg_build_rsa
- NC1 user: ffg-builder (non-root, isolated)
- rsync source with --exclude build/ .gradle/
- ./gradlew build --no-daemon on NC1
- rsync jar back, jar -tf integrity check
- trap cleans workspace on exit/drop
2026-04-12 16:22:43 -05:00
Claude (Chronicler #83 - The Compiler)
bfb9d4dba0 WIP: Discord Rules mod fork — 2/3 versions built, 1.21.1 blocked on RAM
Source code complete for all 3 versions (1.21.1, 1.20.1, 1.16.5).
1.20.1 and 1.16.5 jars built successfully. 1.21.1 NeoForge decompiler
needs >4GB RAM — this server only has ~4GB total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:44:27 -05:00
Claude
6ed12cdeb0 Bridge: REQ-2026-04-12-discord-rules-fork — Task #69 spec for Code
- Full fork spec: com.firefrostgaming.rules -> com.discordrules
- Configurable header/body colors via TOML display section
- Strip Firefrost-specific emoji replacements
- emoji stripping as config toggle
- Build instructions for all 3 MC versions
2026-04-12 20:10:05 +00:00
Claude (Chronicler #83 - The Compiler)
a305c7e686 bridge: Archive Phase 11 spec, update ACTIVE_CONTEXT
- Archived RES-2026-04-12-phase11-implementation-spec from Chronicler
- Updated status with completed code review and next steps (Phase 11A-G)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:12:40 -05:00
Claude (Chronicler #83 - The Compiler)
91432dad98 bridge: Response — Phase 11 complete implementation spec
Distilled from 2 Gemini consultations into actionable checklist.
Database schema, API routes, Discord bot, Blueprint extension changes,
pricing lock, and deployment notes all included.

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 14:09:09 -05:00
Claude (Chronicler #83 - The Compiler)
08a52ee3cf docs: Lock ModpackChecker pricing in CLAUDE.md
Standard $14.99 / Professional $24.99 — two-tier model.
This pricing is FINAL per original marketing strategy.

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 14:05:53 -05:00
Claude (Chronicler #83 - The Compiler)
b0aa52c2c8 fix(blueprint): Review fixes — API key lookup, tier enforcement, safety
Fixes 10 issues from Blueprint extension code review:
- CurseForge API key now reads via Blueprint dbGet() matching admin save
- PRO-tier fields (webhook, interval) enforced server-side, not just UI
- json_decode results validated before accessing parsed data
- Null user guard on getStatus() endpoint
- 429 response uses consistent error key format
- Modrinth slug derivation strips special chars, documented as fallback
- Check interval dropdown reflects saved value
- API key input changed to password type
- TypeScript error typing narrowed from any to unknown
- Removed unused DB import from ModpackApiService

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:53:37 -05:00
Claude (Chronicler #83 - The Compiler)
3457b87aef fix(modpack-checker): Code review fixes — license, safety, and polish
Fixes 10 issues from full code review:
- License corrected from MIT to Commercial
- Deprecated datetime.utcnow() replaced with timezone-aware alternative
- PHP array bounds checks added for all platform API responses
- Modrinth file detection now derives project slug instead of using MC version
- validate_api_key() no longer swallows network errors
- HTTP timeouts added to all external API calls in PHP
- Empty API key rejection added to CLI
- Corrupted config now warns on stderr instead of failing silently
- Error response format made consistent across controller
- Docs updated with correct repo URL and clearer CurseForge ID instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:37:26 -05:00
Claude (Chronicler #83 - The Compiler)
c6d40dcf39 feat: Code ↔ Chronicler bridge protocol
Implemented git-based async communication between Claude Code and Chronicler:
- docs/code-bridge/requests/ — Code files architectural questions here
- docs/code-bridge/responses/ — Chronicler drops distilled Gemini answers
- docs/code-bridge/status/ACTIVE_CONTEXT.md — rolling status updates
- docs/code-bridge/archive/ — completed request/response pairs
- CLAUDE.md updated with full bridge protocol instructions
- Auto-commit-and-push on all bridge writes

Designed with Gemini consultation (2026-04-12).

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 12:15:36 -05:00
Claude (Chronicler #83 - The Compiler)
4ffae74a2f docs: Add Gemini consultation references to CLAUDE.md
Operations manual cloned to Dev Panel for reference.
Claude Code now knows where to find architectural consultations
before making design decisions.

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 12:04:45 -05:00
Claude (Chronicler #83 - The Compiler)
cffe742093 docs: Add root CLAUDE.md for Claude Code workspace
Single workspace config covering all projects, tools, and active tasks.

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 11:58:38 -05:00
Claude (Chronicler #83 - The Compiler)
179bac2911 feat(rules-mod): Add Firefrost Rules Mod source — all 3 versions
NeoForge 1.21.1, Forge 1.20.1, Forge 1.16.5
All compiled and deployed to NextCloud (Task #136)
Source committed for Task #138 (CurseForge generic fork)
CLAUDE.md with build environment docs included

Claude (Chronicler #83 - The Compiler) <claude@firefrostgaming.com>
2026-04-12 11:56:15 -05:00
Claude (Chronicler #82)
240a4776f6 The Forge module + collapsible sidebar nav
New Module: The Forge (/admin/forge)
- AI knowledge assistant powered by Gemma 4 via Dify RAG
- Streaming SSE chat interface with markdown rendering
- Think-tag filtering for Gemma 4's reasoning tokens
- Conversation continuity via Dify conversation IDs
- Source citations from knowledge base documents
- Fire/Frost/Arcane gradient branding
- Welcome screen with suggestion buttons
- Env vars: DIFY_API_URL, DIFY_APP_KEY

Sidebar Navigation Overhaul (layout.ejs)
- The Forge featured prominently at top with gradient border
- Collapsible category groups: Core, Revenue, Community, Operations
- localStorage persistence for collapsed/expanded state
- CSS transitions for smooth collapse animation

Chronicler #82 | April 12, 2026
2026-04-12 02:14:12 -05:00
Claude
1ca6ef4dfa Merge branch 'task-125-asset-browser' 2026-04-12 01:10:13 +00:00
Claude
d22ff8c3c9 feat(admin): Task #125 Phase 2 — Branding asset browser
Adds a visual asset library to the social calendar form, backed by
the firefrost-operations-manual branding/ directory with on-the-fly
thumbnail generation via sharp.

- src/routes/admin/branding-assets.js: /list scans both branding/ and
  docs/branding/ recursively, groups by category directory. /thumb
  generates 256px webp thumbnails from source, caches to disk keyed
  by sha1(path + mtime) so edits bust the cache automatically. Path
  traversal protection + scope check on thumb requests.
- src/views/admin/social-calendar/_assets.ejs: modal body with
  category-grouped grid of lazy-loaded thumbnails, click-to-insert.
- Form modal gets a 'Browse assets' button next to Media Notes.
- Calendar shell gets a second modal (higher z-index) and a
  sppInsertAsset() helper that appends the clicked filename to the
  media_notes textarea.

Infrastructure (not in repo):
- /opt/firefrost-ops-manual clone added to Command Center
- /etc/systemd/system/firefrost-ops-sync.{service,timer} pulls
  every 15 minutes to keep assets fresh
- /var/cache/arbiter/branding-thumbs created for thumb cache
- sharp added as Arbiter dependency (libvips 8.17.3)

Chronicler #81
2026-04-12 01:10:09 +00:00
Claude
bad8036114 Merge branch 'task-125-social-calendar' 2026-04-12 00:54:22 +00:00
Claude
541fb26e0b feat(admin): Task #125 — Social Content Calendar widget
Week-at-a-glance post planning for Meg across 8 platforms (tiktok,
facebook, instagram, x, bluesky, youtube, twitch, reddit).

- DB: extends social_platform enum with youtube/twitch/reddit and adds
  new social_post_plans table with scheduled_at, platforms[], status
  (draft/ready/scheduled/published/skipped), caption, hashtags[],
  media_notes, link, assigned_to, notes, created_by
- src/routes/admin/social-calendar.js: shell, HTMX week view, create,
  update, delete, edit/new form endpoints. Week-based navigation with
  prev/next/this-week. Hashtag parsing dedupes and strips leading #
- src/views/admin/social-calendar/: index.ejs shell with modal,
  _week.ejs 7-column grid with per-day quick-add, _form.ejs full CRUD
  form with platform checkboxes, datetime picker, status dropdown,
  both free-form caption and dedicated hashtag field
- Nav link added under Community, with corrected highlight logic so
  /admin/social and /admin/social-calendar don't both light up

Separate table from social_posts (which remains the analytics table).
Plans and published posts are distinct concerns.

Chronicler #81
2026-04-12 00:54:19 +00:00
Claude
f8eabf3881 Merge branch 'task-126-appeals-reopen' 2026-04-12 00:27:23 +00:00
Claude
b156c0b63f fix(appeals): always show action controls + add Reopen
The 'resolved' state was a dead-end — no way to change your mind after
clicking approve/deny, and no way to re-action a needs_info appeal once
the submitter replied. Action controls now show on every row regardless
of status, and resolved rows get a Reopen button that moves the appeal
back to pending.

Backend action handler already overwrites status unconditionally, so
this is purely removing a view-layer gate plus adding /:id/reopen as
a thin wrapper over the shared actionAppeal helper.

Chronicler #81
2026-04-12 00:27:19 +00:00
Claude
30a1920d58 Merge branch 'task-126-appeals-admin' 2026-04-12 00:18:05 +00:00
Claude
c1e17496c8 feat(admin): Task #126 — Trinity Appeals admin module
Adds /admin/appeals to Trinity Console for reviewing and actioning
ban appeals submitted via the public cancellation-refund form.

- src/routes/admin/appeals.js: shell + HTMX list + approve/deny/info
  actions, all transactional with admin_audit_log entries
- src/views/admin/appeals/index.ejs: shell with 30s polling container
- src/views/admin/appeals/_list.ejs: stats cards + actionable table
- src/routes/admin/index.js: router mount at /admin/appeals
- src/views/layout.ejs: nav link under Grace Period

Closes the last open piece of Task #126 Phase 2. Trinity can now
review appeals in-console without reading Discord + running SQL.

Chronicler #81
2026-04-12 00:18:00 +00:00
Claude
b7330653ab Merge branch 'task-126-lifecycle-handlers'
Task #126 lifecycle handlers deployed to Command Center at 18:05 UTC.
Verified clean startup. Webhook endpoint returns expected HTTP 400 on
unsigned requests. Backup preserved at:
/opt/arbiter-3.0/src/routes/stripe.js.backup-task126-20260411-180439
2026-04-11 23:05:33 +00:00
Claude
9777a7af9d feat(arbiter): Task #126 lifecycle handlers — We Don't Kick People Out
Implements the Firefrost core policy that Awakened ($1) is lifetime access.
Paying customers are never removed from access — only demoted to Awakened
when a paid subscription ends. Hard bans reserved for actionable violations
(chargebacks and refunds).

Changes:
- customer.subscription.deleted: demote to Awakened instead of grace period
  * Uses existing 'lifetime' status + is_lifetime=TRUE (no schema changes,
    no touches to other filters in servers.js/roles.js/financials.js)
  * Calls downgradeToAwakened() to strip tier role and assign Awakened
  * Audit log entry tagged with policy
- New charge.refunded handler: hard ban with Trinity appeal eligibility
  * Mirrors charge.dispute.created pattern
  * appeal_eligible: true flag in banned_users.notes
  * removeAllRoles() (NOT demote — refund is a hard ban)
- Added downgradeToAwakened to stripe.js imports

Left unchanged (intentionally):
- invoice.payment_failed: marks past_due, trust Stripe's retry logic
- invoice.payment_succeeded: clears grace period (harmless legacy field reset)
- charge.dispute.created: already correct under new policy
- checkout.session.completed: syncRole already handles Model A (Awakened
  automatically assigned on any first purchase via tier role replacement)

Co-authored with Michael (The Wizard)
Task #126 — Soft launch blocker
2026-04-11 23:04:30 +00:00
Claude (Chronicler #78)
c07c29c60c feat: Add servers-api Cloudflare Worker to git (recovered from dashboard)
Worker proxies Pterodactyl client API for website server list.
Live status + player counts, CORS-locked, 60s cache.
Was only in Cloudflare Dashboard — known gap now closed.
Recovered via Cloudflare MCP connector audit.

Chronicler #78 | firefrost-services
2026-04-11 18:00:44 +00:00
Claude (Chronicler #78)
aca7669243 feat: Add spec_path field — link tasks to git documentation
- spec_path column added to tasks table
- API POST/PATCH support spec_path field
- Discord /tasks detail view shows '📄 Full Spec' link to Gitea when spec exists
- Backfilled 7 existing tasks with spec directory paths

Architecture: Database tracks state, Git stores documentation, they link to each other.

Chronicler #78 | firefrost-services
2026-04-11 14:38:24 +00:00
Claude (Chronicler #78)
b8f9926e9b feat: Task #116 — Trinity Console Tasks Module
- Tasks page at /admin/tasks with filterable table
- Status/owner inline dropdowns (change via form submit)
- + New Task modal with title, description, priority, owner
- ✓ Done button on hover per row
- Stats bar: active, in progress, blocked, high priority, completed
- Show All toggle for done/obsolete tasks
- Sidebar link under Operations (right after Dashboard)
- Added to About page module registry

Source of truth: PostgreSQL tasks table (shared with Discord /tasks)

Chronicler #78 | firefrost-services
2026-04-11 14:22:21 +00:00
Claude (Chronicler #78)
78179de2bc feat: /tasks detail view + In Progress button
- /tasks number:26 shows full task detail embed
  (description, tags, status, priority, owner, dates)
- Mark Done, In Progress, and Take Task buttons on detail view
- task_progress_ button handler sets status to in_progress

Chronicler #78 | firefrost-services
2026-04-11 14:13:16 +00:00
Claude (Chronicler #78)
48f74e8658 feat: ChatOps Task Management System (Gemini-architected)
Database:
- tasks table in PostgreSQL (id, task_number, title, status, priority, owner, tags)
- 45 tasks migrated from BACKLOG.md + tasks-index files
- Indexes on status, priority, owner, task_number

API (for Chroniclers/Catalysts):
- GET /api/internal/tasks — list with filters (status, priority, owner)
- GET /api/internal/tasks/summary — stats by status and priority
- POST /api/internal/tasks — create new task (auto-numbers)
- PATCH /api/internal/tasks/:id — update status/priority/owner

Discord (for Meg/Holly/Michael):
- /tasks command with filter options (open, in_progress, mine, high, active, done, all)
- Mark Done buttons — one tap to complete a task
- Take Task buttons — claim unassigned tasks
- Color-coded priority and status emoji
- Staff-only access

Architecture: PostgreSQL → Arbiter API → Discord buttons (ChatOps)
Gemini consultation: gemini-task-management-redesign-2026-04-11.md

Chronicler #78 | firefrost-services
2026-04-11 13:56:45 +00:00
Claude (Chronicler #78)
075ab899c5 fix: Add MCP Logs to About page module registry
Chronicler #78 | firefrost-services
2026-04-11 13:18:05 +00:00
Claude (Chronicler #78)
03974d1f13 feat: Task #109 — MCP Logging in Trinity Console (v2.3.0)
Arbiter changes:
- POST /api/internal/mcp/log endpoint in api.js
- MCP Logs admin route (/admin/mcp-logs) with filters, stats, pagination
- EJS view with expandable detail rows, server color badges
- Sidebar link under System group

Trinity Core v2.3.0:
- logToArbiter() function POSTs to Arbiter after every command
- Both MCP (Claude.ai) and REST (/exec) paths log with execution timing
- Async logging — doesn't block command response

Database:
- mcp_logs table created on Command Center (indexes on server, time, success)

Architecture:
  Trinity Core → command → response + async POST → Arbiter → PostgreSQL → Trinity Console

Chronicler #78 | firefrost-services
2026-04-11 11:55:22 +00:00
Claude (Chronicler #78)
0b61d38419 feat: Dashboard — Social Overview above New Features, add new modules
- Moved Social Overview card above New Features card
- Added Infrastructure module to New Features (topology map)
- Added Social Analytics to New Features (engagement tracking)
- Added About & Deploy to New Features (version info)
- Kept Discord Dashboard in New Features
- Removed Financials from New Features (it's established now)

Chronicler #78 | firefrost-services
2026-04-11 10:51:22 +00:00
Claude (Chronicler #78)
bd783093a9 feat: Sidebar reorganization + About page
Sidebar changes:
- Grouped nav items: Operations, Business, Community, Infrastructure, System
- Section labels with uppercase tracking
- Removed Deploy button from sidebar
- Cleaner layout with overflow scroll

About page (/admin/about):
- Console version, Node.js version, Arbiter uptime, module count
- Module registry with version and status badges (stable/new/beta)
- Deploy Arbiter button (moved from sidebar)
- Health check polling after deploy
- Credits footer

Header bar:
- Added ℹ️ About icon next to dark mode toggle
- Active state highlighting when on About page

Chronicler #78 | firefrost-services
2026-04-11 10:46:42 +00:00
Claude (Chronicler #78)
4788140c2c fix: Node clicks blocked after dragging — reset dragMoved on mouseup/touchend
dragMoved flag was only reset in wrap mousedown, which skips for node clicks.
After any drag, dragMoved stayed true forever, blocking all showServer/showExternal calls.
Fix: setTimeout reset on mouseup/touchend (10ms delay lets click fire first).

Chronicler #78 | firefrost-services
2026-04-11 10:38:32 +00:00
Claude (Chronicler #78)
0c7dad36ea feat: Add zoom/pan/pinch to Infrastructure topology
- Mouse wheel zoom (centered on cursor position)
- Click-drag to pan
- Touch pinch-zoom for mobile
- Touch drag to pan on mobile
- Zoom controls: +, −, percentage display, reset (⌂)
- Zoom range: 50% to 300%
- Drag guard prevents accidental clicks after panning
- Canvas connections redraw correctly at all zoom levels
- Smooth CSS transitions on zoom, disabled during drag

Chronicler #78 | firefrost-services
2026-04-11 10:35:03 +00:00
Claude (Chronicler #78)
2e3d272e26 fix: RAM percentage parsing — handle Mi vs Gi units
Panel VPS and Trinity Core report used RAM in Mi (e.g. 703Mi)
while total is in Gi (e.g. 1.9Gi). parseFloat gave 703/1.9 = 37000%.
Now normalizes both values to Gi before calculating percentage.

Chronicler #78 | firefrost-services
2026-04-11 10:30:43 +00:00
Claude (Chronicler #78)
55385634f2 feat: Add REST API endpoints to Trinity Core (v2.2.0)
- GET /servers — list available servers (for Arbiter)
- POST /exec — execute command on server (for Arbiter)
- REST endpoints run alongside MCP protocol
- MCP for Claude.ai connector, REST for Arbiter internal calls
- Fixes Infrastructure module 404 errors
- Version bump 2.1.0 → 2.2.0

Chronicler #78 | firefrost-services
2026-04-11 10:27:08 +00:00
Claude (Chronicler #78)
a83766efb4 feat: Add Infrastructure module to Trinity Console
New module: /admin/infrastructure
- Interactive topology map showing all 8 servers + external services
- Live data from Trinity Core MCP API (cached 60s)
- Bezier curve connections color-coded by type (external/internal/SSH/MCP)
- Hover highlights all connections to/from a node
- Click to drill into server detail (CPU, RAM, disk, load, services)
- Game server list for TX1/NC1 with RAM allocation totals
- External service detail (Cloudflare, Stripe, Discord, Claude.ai)
- Fleet summary bar (8 servers, 22 games, ~42 containers)
- Refresh button forces cache clear + re-audit
- Mobile responsive grid layout
- JetBrains Mono typography, Fire/Frost/Arcane color scheme

Files:
- src/routes/admin/infrastructure.js (route + Trinity Core API client)
- src/views/admin/infrastructure/index.ejs (topology + detail views)
- Modified: src/routes/admin/index.js (registered infrastructure router)
- Modified: src/views/layout.ejs (added nav link)

Chronicler #78 | firefrost-services
2026-04-11 10:24:05 +00:00
Claude (Chronicler #78)
5c980ea681 feat: Add Trinity Core MCP server to version control (v2.1.0)
- Added services/trinity-core/ with index.js, package.json, .gitignore
- v2.1.0 adds local self-execution (trinity-core can audit itself)
- Added executeLocal() function for localhost commands
- SERVERS object now includes trinity-core with local: true flag
- Version bumped from 2.0.0 to 2.1.0
- Previously lived only on Pi at ~/mcp-server/ with no backup

Deployment: Edit here, push to Gitea, curl raw file to Pi, restart service

Chronicler #78 | firefrost-services
2026-04-11 10:07:32 +00:00
Claude
483d12c34d Archive obsolete services (arbiter v2.0, whitelist-manager)
Moved to services/_archived/:
- arbiter/ (v2.0.0) - superseded by arbiter-3.0/
- whitelist-manager/ - merged into Trinity Console

Added README explaining what's archived and why.

DO NOT DEPLOY archived services - kept for historical reference only.

Chronicler #76
2026-04-11 08:00:17 +00:00
Claude
7c534b53a4 Fix: Add socialTotals to correct dashboard route file
Was editing wrong file (admin.js vs admin/index.js)

Chronicler #76
2026-04-10 22:26:10 +00:00
Claude
918fb99b87 Add Social Overview card to Trinity Console dashboard
- Shows cross-platform totals (posts, views, likes, comments)
- Breaks down by platform with icons
- Clickable link to full Social Analytics page

Chronicler #76
2026-04-10 22:23:21 +00:00
Claude
74f7876955 Fix social sync: update post_title and post_url on existing posts
Previously only INSERT included these fields, UPDATE ignored them.

Chronicler #76
2026-04-10 22:20:06 +00:00
Claude
274edccf8a Add post_title to TikTok sync
- Request title field from TikTok API
- Truncate to 80 chars for clean display
- Send post_title in sync payload

Chronicler #76
2026-04-10 22:18:14 +00:00
Claude
21b6fa9788 Fix TikTok sync payload to match Arbiter API
- Use platform_post_id + metrics object for sync endpoint
- Use total_followers for snapshot endpoint

Chronicler #76
2026-04-10 22:15:24 +00:00
Claude
942335156f Fix TikTok max_count: API limit is 20, not 50
Chronicler #76
2026-04-10 22:12:08 +00:00
Claude
c0750ea2c1 Fix TikTok sync: use jq instead of Python heredoc
Python heredoc was breaking on emoji characters in video titles.
jq handles JSON parsing properly.

Chronicler #76
2026-04-10 22:08:58 +00:00
Claude
34464a91af Add TikTok sync script with auto token refresh
- sync-tiktok.sh: Syncs video stats and account metrics to Arbiter
- .tiktok-tokens: Initial OAuth tokens (sandbox credentials)
- Auto-refreshes access token on each run (refresh token lasts 1 year)
- Cron: 5 13 * * * for 8:05 AM CT daily

Chronicler #76
2026-04-10 22:05:00 +00:00
Claude (Chronicler #76)
f35325a597 Add internal API routes for n8n social analytics integration
Task #108 Phase 2: Social Analytics Automation

New endpoints (token-based auth via INTERNAL_API_TOKEN):
- POST /api/internal/social/sync - Upsert single post metrics
- POST /api/internal/social/sync/batch - Batch upsert (max 100 posts)
- POST /api/internal/social/snapshot - Upsert account-level stats
- GET /api/internal/social/digest - Summary data for Discord webhook

Architecture:
- Rube MCP -> n8n -> Arbiter /api/internal/* -> PostgreSQL
- Bearer token auth (not session-based)
- COALESCE for partial updates (only update provided fields)

Next: Generate INTERNAL_API_TOKEN and add to .env on Command Center

🔥 Fire + Frost + Foundation = Where Love Builds Legacy 💙❄️
2026-04-10 21:00:19 +00:00
Claude
92e460a90b fix: Add Social Analytics to sidebar navigation
Chronicler #76
2026-04-10 20:27:35 +00:00
Claude
cfdd89377f feat: Add migration-aware deploy script for Trinity Console
- Automatically runs new SQL migrations on deploy
- Tracks applied migrations in .migrations-applied file
- Skips already-applied migrations
- Logs all actions to /var/log/arbiter-deploy.log
- Handles npm install if package.json changes
- Enables Meg/Holly to deploy without backend access

Install: sudo cp scripts/deploy-arbiter.sh /opt/scripts/
         sudo chmod +x /opt/scripts/deploy-arbiter.sh

Chronicler #76
2026-04-10 20:24:48 +00:00
Claude
be2f5eb5a5 fix: Add platform_post_id column per Gemini review
- Added platform_post_id VARCHAR(255) for Phase 2 API matching
- Added composite index on (platform, platform_post_id)
- Future-proofs manual entries for automatic API sync

Chronicler #76 (per Gemini consultation)
2026-04-10 20:19:49 +00:00
Claude
b8ed2095ba feat: Add Social Analytics module to Trinity Console (Task #108)
- Add database migration for social_posts and social_account_snapshots tables
- Create /admin/social route with full CRUD for posts
- Add dashboard view with platform tabs (TikTok, Facebook, Instagram, X, Bluesky)
- Add post entry form matching TikTok Studio metrics
- Add post detail view with update capability
- Add account snapshot form for follower/demographic tracking
- Register social router in admin index

Phase 1: Manual entry dashboard
Phase 2 (future): API integration when approved

Chronicler #76
2026-04-10 20:14:27 +00:00
Claude
685626f13f fix: Use correct env vars PANEL_URL and PANEL_CLIENT_KEY
.env has PANEL_URL and PANEL_CLIENT_KEY, not PTERO_PANEL_URL.

Chronicler #75
2026-04-10 18:00:33 +00:00
Claude
32b378f539 fix: Use Pterodactyl API directly instead of servers-api Worker
Worker was returning HTML error page. Now queries Pterodactyl panel
directly using PTERO_CLIENT_KEY from .env.

Chronicler #75
2026-04-10 15:41:57 +00:00
Claude
811e3046cf feat: Add server status poller for Discord channels (#107)
New service that polls Pterodactyl (via servers-api Worker) every 5 min
and posts/updates status embeds in each game server's -status channel.

- Creates discord_status_messages table to persist message IDs
- Maps server names to channel IDs for all 15 game servers
- Posts new embed or edits existing one (no spam)
- Handles message deletion gracefully (re-posts if needed)

Status channels created:
- stoneblock-4-status, society-sunlit-valley-status, atm10-tts-status
- all-the-mons-status, mythcraft-5-status, beyond-depth-status
- beyond-ascension-status, otherworld-status, deceasedcraft-status
- submerged-2-status, sneaks-pirate-pack-status, cottage-witch-status
- farm-crossing-5-status, homestead-status, wolds-vaults-status

Chronicler #75
2026-04-10 15:39:04 +00:00
Claude
0acea3b95f fix: Skip OAuth if already logged in, go straight to checkout
If user has existing session from Trinity Console login,
/stripe/auth now redirects directly to /stripe/checkout
instead of re-triggering Discord OAuth.

Chronicler #75
2026-04-10 15:06:34 +00:00
Claude
2740dc5fd3 fix: Use OAuth state parameter instead of session for tier
Session was being lost between /stripe/auth and /auth/discord/callback.
Now passes tier through Discord OAuth state parameter which survives
the redirect.

Chronicler #75
2026-04-10 15:05:40 +00:00
Claude
b4280dc630 db: Add stripe_products seed data with actual Stripe IDs
Maps tier levels to Stripe product/price IDs:
- Tier 1: Awakened ( one-time)
- Tier 2-3: Elemental Fire/Frost (/mo)
- Tier 4-5: Knight Fire/Frost (0/mo)
- Tier 6-7: Master Fire/Frost (5/mo)
- Tier 8-9: Legend Fire/Frost (0/mo)
- Tier 10: Sovereign (99 one-time)

Run on Command Center:
PGPASSWORD='FireFrost2026!Arbiter' psql -U arbiter -h 127.0.0.1 -d arbiter_db -f seed-stripe-products.sql

Chronicler #75
2026-04-10 14:59:15 +00:00
Claude
12ffdd45f5 fix: Add Discord OAuth → Stripe checkout flow
THE BUG: Website redirected to /stripe/auth but route didn't exist.
Checkout sessions were created WITHOUT client_reference_id (Discord ID),
so webhook couldn't sync Discord roles after payment.

THE FIX:
- Added GET /stripe/auth - stores tier in session, redirects to Discord OAuth
- Added GET /stripe/checkout - creates checkout WITH client_reference_id
- Updated auth callback to redirect to /stripe/checkout after OAuth
- Legacy POST /create-checkout-session kept for compatibility

FLOW NOW:
1. User clicks Subscribe on website
2. → /stripe/auth?tier=X (stores tier, redirects to Discord)
3. → /auth/discord (Discord OAuth)
4. → /auth/discord/callback (user authenticated)
5. → /stripe/checkout?tier=X (creates Stripe session WITH Discord ID)
6. → Stripe Checkout (user pays)
7. → Webhook receives event with client_reference_id
8. → Discord role synced!

Chronicler #75
2026-04-10 14:58:41 +00:00
Claude
d227bce0a8 Add discord-channel-rename.js script
- Maps Pterodactyl servers to Discord categories
- Renames channels to consistent naming convention
- DRY_RUN=true by default (preview mode)
- 1.5s rate limit delay between operations
- Snart Doctrine: built to adapt when reality hits
2026-04-09 20:12:12 +00:00
Claude
47a600eeb5 Fix: Handle server names with subtitles for Discord channel matching
- 'Homestead - A Cozy Survival Experience' now matches 'homestead-chat'
- 'All The Mons (Private) - TX' now matches 'all-the-mons-chat'
- Strips subtitles after ' - ' and removes parentheticals
2026-04-09 20:03:41 +00:00
Claude
e30ff4d694 Fix: Add Discord channel status to matrix body template (inline cards, not partial) 2026-04-09 19:59:36 +00:00
Claude
081bad1279 Add Discord channel status check to server cards
- Checks for 4 channels per server: chat, in-game, forum, voice
- Shows 'All 4 channels configured' or lists missing channels
- Caches Discord channel data for 5 minutes to reduce API calls
2026-04-09 19:55:41 +00:00
Claude
cbf5d219fc Add health check after deploy - confirms Arbiter restarted successfully 2026-04-09 19:50:17 +00:00
Claude
02bddc0baf Fix deploy button: use detached process to avoid 502 on self-restart 2026-04-09 19:48:04 +00:00
Claude
ef562ef59a Add Trinity Console deploy button for Holly/Meg/Michael
- Deploy button in sidebar above username
- POST /admin/system/deploy endpoint
- Updated deploy.sh with locking, logging, user tracking
- Prevents concurrent deploys (mkdir lock)
- Logs who deployed and what commit
- Updated DEPLOYMENT.md with setup instructions

Gemini consultation: confirmed synchronous approach, locking, sudoers config
2026-04-09 19:40:34 +00:00
Claude
dc59e5c1de Add /delserver documentation script
Chronicler: #71
2026-04-08 17:30:12 +00:00
Claude
69200d8ac3 Add /delserver slash command
Deletes complete server setup:
- All channels in category
- The category itself
- The server role

Requires confirm:True to execute.
Without confirm, shows preview of what would be deleted.
Reminds to clean up Carl-bot reaction roles.

Staff only.

Chronicler: #71
2026-04-08 17:27:10 +00:00
Claude
7ecce5da8f Add script to create #staff-commands with documentation
Creates channel in Staff Area with detailed embeds for:
- /link command (everyone)
- /createserver command (staff only)

Chronicler: #71
2026-04-08 17:21:52 +00:00
Claude
06f7afe25d Add /createserver slash command
Creates complete server setup with one command:
- Creates role
- Creates category with 🎮 prefix
- Creates chat, in-game, forum, voice channels
- Applies permission template
- Posts and archives welcome message
- Suggests unused emoji for reaction roles

Staff only. Reminds to configure Carl-bot.

Task #98 Discord Channel Automation
Chronicler: #71
2026-04-08 17:18:28 +00:00
Claude
083885c874 Add emoji prefixes to remaining categories
📢 Welcome & Info
💬 Community Hub
🔊 Voice Channels
📞 Support

Chronicler: #71
2026-04-08 17:05:34 +00:00
Claude
05d23e2dfc Add script to archive welcome posts
Fixes forum channels staying visible when category collapsed.
Archived threads don't count as 'active'.

Chronicler: #71
2026-04-08 17:00:17 +00:00
Claude
940840d69a Fix Wold's Vaults v2 - use role ID directly
Whatever apostrophe variant that is, we're bypassing it.
Role ID: 1491029373640376330

Chronicler: #71
2026-04-08 16:53:42 +00:00
Claude
f5a75d204f Fix Wold's Vaults - curly apostrophe
Role uses ' (curly) not ' (straight)

Chronicler: #71
2026-04-08 16:52:36 +00:00
Claude
40cb6cef31 Add full Discord channel setup script (46 channels)
Task #98 implementation:
- Phase 1: Add 🎮 prefix to existing 5 server categories
- Phase 2: Add forums to existing 5 servers
- Phase 3: Create full setup for 10 new servers (category + chat + in-game + voice + forum)
- Phase 4: Create 📦 Archive category (staff-only)

Includes:
- DRY_RUN mode for safe testing
- Permission overwrites (Wanderer=view, Server Role=interact, Staff=full)
- 15 welcome posts with server-specific content
- 6 standard forum tags per forum
- Rate limiting (500ms between API calls)

Chronicler: #71
2026-04-08 16:50:05 +00:00
Claude
9752c6fd89 Add full Discord channel setup script (46 channels)
Task #98: Discord Channel Automation
- Phase 1: Add forums to existing 5 servers + rename categories
- Phase 2: Create 10 new server categories with all channels
- Phase 3: Create Archive category (staff only)

Includes:
- 15 server-specific welcome posts
- Standard forum tags (6 tags)
- Permission template (Wanderer view-only, Server Role full access)
- Rate limiting (500ms delays)
- Idempotent (skips existing channels)

Chronicler: #71
2026-04-08 16:48:48 +00:00
Claude
911f5801fc Fix .env path to /opt/arbiter-3.0/.env
Chronicler: #71
2026-04-08 16:40:40 +00:00
Claude
8768c6773f Add Discord channel creation test script
Phase 1 test for Task #98 Discord automation.
Creates one test category + one forum with tags + welcome post.
Includes DRY_RUN mode and permission checks.

Chronicler: #71
2026-04-08 16:39:48 +00:00
Claude Chronicler-70
9e4fa13fdb feat(arbiter): Add New Features card to dashboard
Highlights Discord Dashboard and Financials Module with
clickable cards that link directly to the new features.

Chronicler: #70
2026-04-08 15:33:54 +00:00
Claude Chronicler-70
b96ab1fb24 feat(arbiter): Add Discord dashboard to Trinity Console
- New sidebar entry for Discord
- Full server structure visualization
- Channel tree with expandable categories
- Role hierarchy with color badges
- Health checks (orphan channels, empty roles, bot roles)
- Search/filter across channels and roles
- Click channel to see permission overwrites
- Click role to see explicit channel access
- Responsive design with modal details view

Chronicler: #70
2026-04-08 15:30:22 +00:00
Claude Chronicler-70
04bc2e734f feat(arbiter): Add localhost bypass for admin routes debugging 2026-04-08 15:23:20 +00:00
Claude
b639f92da6 fix: Remove incorrect middleware import from discord-audit
Parent router (admin/index.js) already applies requireTrinityAccess
to all child routes. No additional auth middleware needed.

Chronicler #70
2026-04-08 15:19:50 +00:00
Claude
e99ef3b942 feat: Add Discord audit routes to Arbiter
New endpoints for Trinity Console:
- GET /admin/discord/audit — Full server audit (channels, roles, structure)
- GET /admin/discord/channels — Just channels
- GET /admin/discord/roles — Just roles

Returns:
- Server info (name, member count, features)
- Categories with nested children
- Orphan channels (not in categories)
- Role hierarchy with positions and member counts
- Permission overwrites per channel

Uses existing Discord.js client from app.locals.

Chronicler #70
2026-04-08 15:15:20 +00:00
Claude
7cf0eec2db Add module list to v2 teaser
12 planned modules: Dashboard, Players, Servers, Infrastructure,
Financials, Tasks, Docs, Team, Marketing, Chroniclers, System, Health

Chronicler #69
2026-04-08 08:55:17 +00:00
Claude
20b2fab994 Add Trinity Core v2 teaser to dashboard
Features highlighted:
- Trinity Codex AI (natural language queries)
- Smart Notifications (Discord alerts)
- Approval Workflows (Discord button approvals)
- Plugin Architecture (self-registering modules)
- Granular Permissions (RBAC for staff)
- Distributed Mesh (Tailscale-connected servers)

Styled with brand gradient border.

Chronicler #69
2026-04-08 08:53:56 +00:00
Claude
c7c2340321 Add logout button to user profile in sidebar
- Door emoji button next to username
- Links to /auth/logout
- Redirects to home page after logout

Chronicler #69
2026-04-08 08:50:32 +00:00
Claude
460d36c9b2 Remove placeholder notification bell
Non-functional UI element. Notifications will be implemented
properly in Trinity Core.

Chronicler #69
2026-04-08 08:48:38 +00:00
Claude
5bd4c60238 Fix scheduler timezone labels: UTC → Central
Times are stored in Central Time (matching Pterodactyl server config).
Labels were incorrectly showing UTC.

Chronicler #69
2026-04-08 08:46:07 +00:00
Claude
795020b55c Add Export CSV button to Players page
- Exports all players with full subscription data
- Includes: discord_id, minecraft_username, minecraft_uuid, is_staff,
  tier_level, tier_name, status, mrr_value, is_lifetime,
  stripe_customer_id, created_at, updated_at
- Downloads as firefrost-players-YYYY-MM-DD.csv
- Properly escapes CSV values with quotes

Chronicler #69
2026-04-08 08:41:17 +00:00
Claude
a13d9a2c66 Add 10-minute retry for failed server syncs
When hourly sync encounters servers that fail (e.g., mid-restart):
- Logs the failure count
- Schedules automatic retry in 10 minutes
- Retry only targets previously failed servers
- Clears error state on successful retry

Fixes issue where servers in daily restart would stay in error state
until manual intervention.

Chronicler #69
2026-04-08 08:39:34 +00:00
Claude
c2b6610e6d Add version number (v1.0) below Trinity Console title
Small text under the logo in sidebar for version tracking.

Chronicler #69
2026-04-08 08:34:41 +00:00
Claude
7d21b4290a Dashboard: Show last sync date/time instead of just checkmark
- Queries server_sync_log for most recent successful sync
- Displays date (e.g., 'Apr 8') and time (e.g., '3:28 AM')
- Shows yellow dash with 'Never' if no syncs recorded

Chronicler #69
2026-04-08 08:32:28 +00:00
Claude
7f990933df Sync package.json with production dependencies
Added missing dependencies that were installed on server but not in repo:
- axios: ^1.14.0
- connect-pg-simple: ^10.0.0
- date-fns: ^4.1.0

This caused deploy script to fail with MODULE_NOT_FOUND errors.

Chronicler #69
2026-04-08 08:30:17 +00:00
Claude
d121bd21f6 Fix dashboard SQL: use tier_level and mrr_value columns
The subscriptions table uses:
- tier_level (integer) not tier_id
- mrr_value (pre-calculated) not joined to subscription_tiers
- is_lifetime (boolean) not status='lifetime'

Chronicler #69
2026-04-08 08:24:08 +00:00
Claude
91eea2c5ff Add Arbiter deployment script and documentation
Created:
- deploy.sh: One-command deployment script
- DEPLOYMENT.md: Full deployment guide

Features:
- Handles cleanup of old temp directories
- Shallow clone for speed
- Checks for dependency changes
- Verifies service after restart
- Clear error messages

Usage on Command Center:
  bash /opt/arbiter-3.0/deploy.sh

Or remote curl:
  curl -fsSL https://git.firefrostgaming.com/.../deploy.sh | bash

Chronicler #69
2026-04-08 08:22:22 +00:00
Claude
3666241aac Fix Trinity Console dashboard: dynamic server/subscriber counts
Dashboard was showing hardcoded values:
- Servers Online: 12 (should be 22)
- Active Subscribers: 0
- Total MRR: $0

Now fetches live data:
- Server count from Pterodactyl API via getMinecraftServers()
- Subscriber count and MRR from arbiter_db subscriptions table

Files changed:
- src/routes/admin/index.js: async dashboard route with data fetching
- src/views/admin/dashboard.ejs: EJS variables instead of hardcoded values

Chronicler #69
2026-04-08 08:19:10 +00:00
Claude
567164ef7d Add servers-api Cloudflare Worker to version control
- Retrieved from Cloudflare dashboard via MCP connector
- Was 'dashboard only, not in any git repo' - gap now closed
- Original creation: April 3, 2026 by Chronicler #56 (The Velocity)
- Proxies Pterodactyl API for live server status on website

Chronicler #68
2026-04-08 05:44:00 +00:00
Claude (Chronicler #63)
e59ee04b03 fix(modpackchecker): Change check_interval validation from required to nullable
Disabled PRO fields don't submit values, causing validation error.
Now accepts null and defaults to 'daily' in update method.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 12:59:28 +00:00
Frostystyle
1a3e884186 release(modpackchecker): v1.0.0 packaged blueprint file 2026-04-06 07:53:45 -05:00
Claude (Chronicler #63)
6e15a62378 fix(modpackchecker): Update website link to Discord
conf.yml: Changed website from firefrostgaming.com to firefrostgaming.com/discord
- Discord is the primary support channel
- Link icon in Blueprint admin header now goes directly to support

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 12:45:21 +00:00
Claude (Chronicler #63)
05d2164dce fix(modpackchecker): Console card redesign - StatBlock style + short errors
wrapper.tsx complete rewrite:
- Matches Pterodactyl StatBlock styling exactly
- Uses col-span classes for proper grid layout
- Icon with status color (orange=update, cyan=current, gray=idle)
- Clickable card instead of separate button
- Short error codes for better UX:
  - 'Not configured' (no modpack variables)
  - 'Wait 60s' (rate limited)
  - 'Not found' (404)
  - 'API error' (general failure)
  - 'Check failed' (long error truncated)

build.sh:
- Injects into AfterInformation.tsx (right column)
- Card appears after Network stats

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 12:32:41 +00:00
Claude (Chronicler #63)
c160647f0b fix(modpackchecker): Move card to right column, match StatBlock style
build.sh:
- Changed injection from ServerConsoleContainer to AfterInformation.tsx
- Card now appears in right column after Network stats

wrapper.tsx:
- Redesigned to match Pterodactyl StatBlock aesthetic
- Uses Tailwind classes (bg-gray-600, rounded, etc.)
- FontAwesome cube icon with status colors
- Compact layout: title + Check button on one line
- Fire (#FF6B35/orange-400) for updates, Frost (#4ECDC4/cyan-400) for current

Fixes layout issue identified in Wizard review.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 12:21:19 +00:00
Claude (Chronicler #63)
d735e3d9db fix(modpackchecker): Wizard review fixes - UI polish
Admin Panel (view.blade.php):
- Changed CurseForge API key from password dots to plain text
- Fixed callouts: dark theme (#1a1a2e) with Frost/Fire accent borders
- Improved code tag styling for readability
- Changed support link to firefrostgaming.com/discord

README.md:
- Added Prerequisites section (PHP 8.1+, Node.js 18+, Yarn, Blueprint)

Reviewed by: Michael 'Frostystyle' Krause (The Wizard)
Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 12:13:14 +00:00
Claude (Chronicler #63)
5a607c8c8b refactor(modpackchecker): Batch 3+4 fixes - frontend, admin, docs
BATCH 3 - Frontend & UI:

wrapper.tsx (Console Widget):
- FIXED: API URL from .../ext/modpackchecker/check to .../check
- Added 429 rate limit handling with user-friendly message

UpdateBadge.tsx (Dashboard Badge):
- Added 60-second TTL to global cache (was infinite)
- Prevents stale data during client-side navigation

admin/view.blade.php:
- Disabled Discord webhook field (PRO TIER badge)
- Disabled Check Interval field (PRO TIER badge)
- Added support callout linking to Discord

BATCH 4 - Documentation:

README.md:
- Fixed architecture diagram (server_uuid, status string)
- Added app/Services/ModpackApiService.php to file structure
- Fixed API endpoint URLs throughout
- Updated installation for BuiltByBit (.blueprint package)
- Updated 'Adding New Platform' instructions for Service pattern
- Added Support section with Discord link
- Changed license to explicit commercial terms

NEW: CHANGELOG.md
- Version history for future updates
- Documents v1.0.0 features

Reviewed by: Gemini AI (Architecture Consultant)
Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:47:20 +00:00
Claude (Chronicler #63)
8e37120289 refactor(modpackchecker): Batch 2 fixes - centralized service, rate limiting, schema fixes
NEW: app/Services/ModpackApiService.php
- Centralized API logic for all 4 platforms
- Technic build number cached for 12 hours (RV-Ready)
- Single source of truth for API calls

Controller (ModpackAPIController.php):
- Now uses injected ModpackApiService instead of duplicated code
- Added RateLimiter: 2 requests/minute per server on manualCheck()
- Returns 429 with countdown when rate limited
- Removed 400+ lines of duplicated API code

Console Command (CheckModpackUpdates.php):
- FIXED: updateDatabase() now uses server_uuid (not server_id)
- FIXED: status column uses strings ('update_available', 'up_to_date', 'error')
- FIXED: Technic API now uses dynamic build via service
- Now uses injected ModpackApiService

SECURITY:
- Rate limiting prevents API key abuse via button spam
- Technic build caching reduces external API calls

Reviewed by: Gemini AI (Architecture Consultant)
Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:33:11 +00:00
Claude (Chronicler #63)
35315c2e81 refactor(modpackchecker): Batch 1 fixes from Gemini review
Routes (client.php):
- Removed redundant prefixing - Blueprint auto-prefixes with identifier
- Clean paths: /servers/{server}/check and /status
- Added clear comments documenting resulting URLs

Migration:
- Changed enum('status') to string('status') for future flexibility
- Added foreign key constraint: server_uuid -> servers.uuid with cascade delete
- Ensures 'RV-Ready' data integrity - no ghost data on server deletion

Build Script:
- Removed redundant PHP copy logic (Blueprint handles via requests.app)
- Fixed dead code that referenced wrong path for console command
- More targeted sed patterns for better stability
- Added author/version header

Reviewed by: Gemini AI (Architecture Consultant)
Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:27:46 +00:00
Claude (Chronicler #63)
845d121fb2 chore(modpackchecker): Update authorship for commercial release
All files now credit: Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>

Updated:
- conf.yml author field
- README.md credits section
- ModpackAPIController.php @author tag
- CheckModpackUpdates.php @author tag
- UpdateBadge.tsx @author tag

Removed internal Chronicler references from commercial codebase.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:20:20 +00:00
Claude (Chronicler #63)
517ec996a9 fix(modpackchecker): getStatus() use server_uuid and status column
BUG: Was using server_id (column doesn't exist) instead of server_uuid
BUG: Was using update_available (column doesn't exist) instead of status

FIXED:
- Changed whereIn('server_id', $serverIds) to whereIn('server_uuid', $serverUuids)
- Changed pluck('id') to pluck('uuid')
- Changed (bool) $status->update_available to $status->status === 'update_available'

This fix makes the dashboard badge API actually work!

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:15:22 +00:00
Claude (Chronicler #63)
7437b4fa7b docs(modpackchecker): Fix namespace in README, add icon to file structure
- Changed troubleshooting namespace to Pterodactyl\Http\Controllers
- Added icon.png to file structure documentation

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 10:12:23 +00:00
Claude (Chronicler #63)
6992790104 feat(modpackchecker): Add Gemini-designed extension icon
- Isometric cube with checkmark (version check concept)
- Frost (#4ECDC4) edge on left, Fire (#FF6B35) edge on right
- Subtle Firefrost branding that fits Pterodactyl's UI
- 128x128 PNG with transparency

Designed by Gemini AI, April 2026.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 10:09:40 +00:00
Claude (Chronicler #63)
5c97b40237 fix(modpackchecker): Fix Technic API 401 error with dynamic build number
ROOT CAUSE (Gemini consultation):
Technic blocks requests with old/deprecated build numbers. The hardcoded
'?build=1' was being rejected as an ancient launcher version.

SOLUTION:
- Fetch current stable launcher build from /launcher/version/stable4
- Use that build number in the modpack request
- Fallback to 999 if version check fails

This 'RV-Ready' approach requires zero maintenance as Technic updates
their launcher versions over time.

ALL 4 PLATFORMS NOW WORKING:
 Modrinth
 FTB
 CurseForge
 Technic

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 10:01:53 +00:00
Claude (Chronicler #63)
326f6529f3 docs(modpackchecker): Update README with correct structure and Technic status
- Updated file structure to show app/Http/Controllers and app/Console/Commands
- Added explanation of why the app/ folder structure is used
- Updated platform support table with working status
- Added note about Technic API 401 error (investigation needed)

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 09:54:39 +00:00
Claude (Chronicler #63)
0f2ece4f88 fix(modpackchecker): Restructure for Blueprint PSR-4 compliance
BREAKING CHANGES - folder structure reorganized:

OLD STRUCTURE (broken):
  Controllers/ModpackAPIController.php
  console/CheckModpackUpdates.php

NEW STRUCTURE (working):
  app/Http/Controllers/ModpackAPIController.php
  app/Console/Commands/CheckModpackUpdates.php

CHANGES:

1. Moved controller to app/Http/Controllers/
   - Namespace changed: Pterodactyl\Http\Controllers
   - This aligns with Laravel's PSR-4 autoloading
   - Blueprint's requests.app field merges into Pterodactyl's app/

2. Moved console command to app/Console/Commands/
   - Now properly registered with Laravel's command system
   - Run with: php artisan modpackchecker:check

3. Updated conf.yml:
   - Set requests.app: 'app' (enables app/ folder merging)
   - Cleared data.directory (was pointing to non-existent folder)
   - Cleared dashboard.wrapper (TSX not supported, use build.sh)

4. Updated routes/client.php:
   - Fixed use statement to match new namespace

TESTED AND VERIFIED:
- blueprint -build: SUCCESS
- yarn build:production: SUCCESS
- php artisan modpackchecker:check: SUCCESS
- API tests passed: Modrinth , FTB , CurseForge 
- Technic API now requires auth (needs investigation)

This commit represents the WORKING state deployed on Dev Panel.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 09:52:57 +00:00
Claude (Chronicler #63)
e36b20d06e docs(modpackchecker): Comprehensive developer documentation
Added professional-grade documentation throughout the codebase so any
developer can pick up this project and understand it immediately.

PHILOSOPHY:
'Hand someone the repo and say: here's what we built, here's WHY we built
it this way, here's where it's going. Make it better.' — Michael

NEW FILES:
- blueprint-extension/README.md
  - Complete developer onboarding guide (400+ lines)
  - Architecture diagram showing cron → cache → badge flow
  - Installation steps, configuration, usage
  - API reference with example responses
  - Troubleshooting guide
  - Design decisions with rationale

ENHANCED DOCUMENTATION:

ModpackAPIController.php:
- 60-line file header explaining purpose, architecture, critical decisions
- Detailed docblocks on every method
- Explains WHY dashboard reads cache-only (rate limits)
- Documents all four platform APIs with links
- Example request/response for each endpoint

CheckModpackUpdates.php:
- 50-line file header with usage examples
- Recommended cron schedule
- Example console output
- Documents rate limiting strategy
- Explains relationship to dashboard badges

UpdateBadge.tsx:
- 50-line file header explaining the 'dumb badge' architecture
- Detailed comments on global cache pattern
- Documents the fetch-once deduplication strategy
- Explains render conditions and why each exists
- Brand color documentation (Fire/Frost)
- Accessibility notes (aria-label)

WHAT A NEW DEVELOPER NOW KNOWS:
1. The 'why' behind every architectural decision
2. How the cron → cache → badge flow prevents rate limits
3. Which methods call external APIs vs read cache
4. How to add a new platform
5. How to troubleshoot common issues
6. The relationship between all components

This codebase is now ready to hand to a contractor with the words:
'This was made great. Make it awesome.'

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 09:05:48 +00:00
Claude (Chronicler #63)
0cbea6d993 feat(modpackchecker): Phase 5 complete - Dashboard badge and cron job
Phase 5 Components (completing Pyrrhus's work):

NEW FILES:
- views/dashboard/UpdateBadge.tsx: Dashboard badge component
  - Shows 🟢 (up to date) or 🟠 (update available) next to server names
  - Global cache prevents multiple API calls on page load
  - Reads from local database, never calls external APIs directly
  - Fire (#FF6B35) and Frost (#4ECDC4) brand colors

- console/CheckModpackUpdates.php: Laravel cron command
  - Run with: php artisan modpackchecker:check
  - Loops through servers with MODPACK_PLATFORM variable
  - Checks CurseForge, Modrinth, FTB, Technic APIs
  - Rate limited (2s sleep between checks)
  - Stores results in modpackchecker_servers table

UPDATED FILES:
- Controllers/ModpackAPIController.php:
  - Added getStatus() method for dashboard badge endpoint
  - Returns all user's servers' update status in single query
  - Added DB facade import

- routes/client.php:
  - Added GET /extensions/modpackchecker/status route

- build.sh:
  - Complete rewrite for Phase 5
  - Handles both console widget AND dashboard badge
  - Auto-detects extension directory (dev vs extensions)
  - Copies CheckModpackUpdates.php to app/Console/Commands/
  - Injects UpdateBadge into ServerRow.tsx
  - Clear status output and next-steps guide

Architecture (Gemini-approved):
  CRON (hourly) → Database cache → Single API endpoint → React badge
  Dashboard badge is 'dumb' - only reads from cache, never external APIs

Completing work started by Chronicler #62 (Pyrrhus).
UpdateBadge.tsx was lost in Blueprint corruption - reconstructed from
handoff notes and architecture documentation.

Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 08:53:27 +00:00
Claude (Chronicler #62)
1eda8894d5 fix: ModpackChecker Phase 3 complete - working end-to-end pipeline
PHASE 3 COMPLETE - All systems operational on Dev Panel

Changes:
- Renamed controllers/ to Controllers/ (PSR-4 case sensitivity fix)
- Updated namespace to use capital C in Controllers
- Fixed getEggVariable() method to use correct Pterodactyl model structure
  - Changed from whereHas('variable'...) to direct where('env_variable'...)
  - Changed return from variable_value to server_value
- Updated routes/client.php with correct namespace
- Updated wrapper.tsx with correct API path (/api/client/extensions/...)
- Added build.sh for React component injection via sed

Tested and verified:
- Admin UI renders correctly
- Client panel loads without 500 error
- React component appears on server console page
- API call executes successfully
- Returns proper 'no modpack detected' message for unconfigured servers

Key learnings documented:
- Blueprint wrapper field is for Blade only, not TSX
- TSX components require build.sh + sed injection + yarn build
- PHP-FPM OPCache requires restart after adding new classes
- Controller namespace must match directory case exactly

Dev Panel: http://64.50.188.14:128
Test Server UUID: c0a133db-6cb7-497d-a2ed-22ae66eb0de8

Next: Phase 4 - Real modpack testing with CurseForge API

Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
2026-04-06 01:39:04 +00:00
Claude (Chronicler #62)
35aded99fe feat(modpackchecker): add Blueprint extension Phase 2 - core architecture
Task #26 Phase 2 Complete — Core Architecture

Files created:
- conf.yml: Blueprint manifest with all paths configured
- admin/controller.php: Admin settings controller (BYOK key, webhook, interval)
- admin/view.blade.php: Admin UI with Trinity-inspired styling
- controllers/ModpackAPIController.php: Client API with all 4 platform integrations
- routes/client.php: Client route for manual version checks
- views/server/wrapper.tsx: React component for server overview page
- database/migrations: Per-server tracking table

Platform Support (all implemented):
- CurseForge (BYOK API key)
- Modrinth (open, no key)
- Technic (open, no key)
- FTB/modpacks.ch (open, no key)

Detection Strategy:
1. Egg Variables (MODPACK_PLATFORM, MODPACK_ID, platform-specific vars)
2. File fingerprinting via DaemonFileRepository (manifest.json, modrinth.index.json)
3. Manual override via admin UI

Next: Phase 3 - Testing on Dev Panel (64.50.188.128)

Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
2026-04-06 00:35:01 +00:00
Claude (Chronicler #62)
1a97e82ec8 feat(arbiter): implement Task #87 - Lifecycle handlers with Discord role sync
WHAT THIS ADDS:
- Discord role sync on new subscriptions (checkout.session.completed)
- Discord role removal on chargebacks (charge.dispute.created)
- Grace period expiration job (hourly cron check)
- Automatic downgrade to Awakened when grace period expires

NEW FILES:
- src/services/discordRoleSync.js - Role add/remove/sync functions
- src/sync/graceExpiration.js - Grace period expiration processor

MODIFIED FILES:
- src/routes/stripe.js - Added role sync calls to webhook handlers
- src/discord/events.js - Initialize role sync service on bot ready
- src/sync/cron.js - Added grace period check to hourly job
- src/index.js - Import discordRoleSync service

PHILOSOPHY:
'We Don't Kick People Out' - expired grace periods downgrade to
permanent Awakened tier (tier 1, lifetime). Users keep community
access, just lose premium perks.

ROLE MAPPING (tier_level -> role key):
1=the-awakened, 2=fire-elemental, 3=frost-elemental,
4=fire-knight, 5=frost-knight, 6=fire-master, 7=frost-master,
8=fire-legend, 9=frost-legend, 10=the-sovereign

CHARGEBACKS:
- Immediate role removal
- Added to banned_users table
- Full audit logging

Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
2026-04-05 14:25:41 +00:00
Claude (Chronicler #61)
bc66fec77a feat: PostgreSQL session store
Replaces MemoryStore with connect-pg-simple.
Sessions now persist across Arbiter restarts.
Table 'session' auto-created if missing.

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:34:44 +00:00
Claude (Chronicler #61)
d9b54187ee fix: Normalize base_time to HH:mm:ss format
HTML time input sends HH:mm, but calculateStagger expects HH:mm:ss.

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:30:36 +00:00
Claude (Chronicler #61)
3e4055c5dc fix: Add CSRF token to update-config form
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:25:07 +00:00
Claude (Chronicler #61)
8a56c920db fix: Remove duplicate code block causing syntax error
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:17:29 +00:00
Claude (Chronicler #61)
22a8a3f92d fix: Simplify audit to catch ALL non-Trinity schedules
Removed power task filter — Pterodactyl doesn't include task
relationships by default. Now catches any schedule not prefixed
with [Trinity].

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:15:14 +00:00
Claude (Chronicler #61)
3ee303244e fix: Use server.identifier instead of server.id in import
Discovery returns 'identifier' field, not 'id'.

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:11:26 +00:00
Claude (Chronicler #61)
71454946e5 fix: Remove EJS includes for express-ejs-layouts compatibility
express-ejs-layouts doesn't support nested includes.
Changed scheduler.ejs to inline the table HTML.
Changed routes to return raw HTML for HTMX partials instead of rendering.

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 10:07:51 +00:00
Claude (Chronicler #61)
5e8201fd22 feat: Task #94 Global Restart Scheduler
Complete implementation of staggered restart scheduler for Trinity Console.

Database:
- global_restart_config: Node-wide settings (TX1 @ 04:00 UTC, NC1 @ 04:30 UTC)
- server_restart_schedules: Per-server state with sort order
- sync_logs: Audit trail for all sync operations

Backend:
- src/utils/scheduler.js: Stagger calculation with date-fns
- src/lib/ptero-sync.js: Pterodactyl API integration (create/update/delete/audit)
- src/routes/admin/scheduler.js: All CRUD + import + sync + audit routes

Frontend:
- Drag-and-drop server ordering (SortableJS)
- Per-node config cards with base time + interval
- Audit modal to detect and nuke rogue schedules
- Skip toggle for maintenance mode
- Visual sync status indicators

Features:
- Import servers from Pterodactyl discovery
- Recalculate effective times on reorder
- Rate-limited API calls (200ms delay)
- [Trinity] Daily Restart naming convention

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 09:58:52 +00:00
Claude (Chronicler #60)
2f67708fcf Add Sync All buttons functionality for server matrix
WHAT WAS DONE:
- Added POST /admin/servers/sync-all/:node endpoint
  - Accepts 'tx1' or 'nc1' as node parameter
  - Syncs whitelist to all servers on that node
  - Returns count of synced/errors

- Wired up buttons in index.ejs with htmx
  - hx-post to the new endpoint
  - Results display in #sync-result span

Files changed:
- services/arbiter-3.0/src/routes/admin/servers.js (+45 lines)
- services/arbiter-3.0/src/views/admin/servers/index.ejs

Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
2026-04-05 08:34:50 +00:00
Claude (Chronicler #60)
e23f44ad67 Restore nest filter for server discovery
WHAT WAS DONE:
- Re-added MINECRAFT_NEST_IDS filtering
- Keeps the node ID mapping fix (2→NC1, 3→TX1)

WHY:
Non-Minecraft servers were appearing in the matrix.
We need to filter to only show Minecraft servers.

Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
2026-04-05 08:32:07 +00:00
Claude (Chronicler #60)
62ddb8b8b6 Remove nest filter from server discovery
WHAT WAS DONE:
- Removed MINECRAFT_NEST_IDS filtering
- Now shows ALL servers from Pterodactyl, not just Minecraft nests

WHY:
Trinity Console should show all servers for management,
not just those in specific nests.

Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
2026-04-05 08:24:42 +00:00
Claude (Chronicler #60)
291b329067 Fix Task #91: Server matrix node detection
WHAT WAS DONE:
- discovery.js: Added node field to server objects
  - Maps Pterodactyl node ID 2 → NC1
  - Maps Pterodactyl node ID 3 → TX1
  - Also includes raw nodeId for debugging

- servers.js: Simplified grouping logic
  - Removed fallback checks for 'Node 2', 'Node 3', name patterns
  - Now uses clean s.node === 'TX1' / 'NC1' checks

THE BUG:
getMinecraftServers() was only returning identifier and name,
but the matrix filter was checking s.node which was undefined.
Servers were being grouped by name pattern fallback only.

Files changed:
- services/arbiter-3.0/src/panel/discovery.js (+8 lines)
- services/arbiter-3.0/src/routes/admin/servers.js (simplified)

Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
2026-04-05 08:23:14 +00:00
575 changed files with 35824 additions and 240 deletions

177
CLAUDE.md Normal file
View File

@@ -0,0 +1,177 @@
# Firefrost Gaming — Local Nitro Workspace
## What This Is
Claude Code is now running locally on Michael's Nitro laptop (AN515-55).
This repo is `firefrost-services` — all services, mods, and tools in one place.
Git remote pushes to git.firefrostgaming.com (Gitea).
## Environment Change (April 15, 2026)
Previously: Claude Code ran via SSH on Dev Panel (64.50.188.128) at `/opt/mod-builds/firefrost-services`
Now: Claude Code runs locally on Nitro at `C:\Users\mkrau\firefrost-services`
**What this means:**
- Java/Gradle builds are NOT available locally — mod compilation must be done on Dev Panel or dispatched via bridge
- All three repos are cloned locally: `firefrost-services`, `firefrost-operations-manual`, `firefrost-website`
- Workflow: edit locally → git push → Chronicler deploys to servers
- The bridge protocol works the same way
## Projects
- `services/rules-mod/` — Minecraft /rules command mod (3 versions)
- `services/modpack-version-checker/` — ModpackChecker (Python CLI + Pterodactyl Blueprint extension)
- `services/arbiter-3.0/` — Arbiter backend (Node.js/Express)
- `services/trinity-core/` — MCP gateway for SSH access
## Java Versions (for mod builds)
- Java 8: `/usr/lib/jvm/java-8-openjdk-amd64` (Forge 1.16.5)
- Java 17: `/usr/lib/jvm/java-17-openjdk-amd64` (Forge 1.20.1)
- Java 21: `/usr/lib/jvm/java-21-openjdk-amd64` (NeoForge 1.21.1)
- Switcher: `source use-java {8|17|21}`
## Gradle (for mod builds)
- Gradle 8.8: `/opt/gradle-8.8` (NeoForge + modern Forge)
- Gradle 7.6.4: `/opt/gradle-7.6.4` (Forge 1.16.5)
## Other Tools
- Python 3.12, PHP 8.3, Node.js 24
- Blueprint beta-2026-01 (Pterodactyl extension framework)
- ModpackChecker Phase 5 backup: `/root/modpackchecker_backup/`
## NextCloud Upload (for mod jars)
```
curl -u "mkrause612:APP_TOKEN" -T file.jar \
"https://downloads.firefrostgaming.com/remote.php/dav/files/mkrause612/Firefrost-Mods/..."
```
## Active Tasks
### Task 138 — Discord Rules Mod (CurseForge Release)
**Goal:** Fork the Firefrost-specific rules mod into a generic version and publish on CurseForge.
**What to do:**
- Create `services/rules-mod/discord-rules/` directory (fork from 1.21.1 code)
- Mod name: "Discord Rules" (not "Firefrost Rules")
- Remove Fire/Frost/Arcane color detection — replace with configurable header_color and body_color in TOML config
- Remove emoji-to-bracket conversion (or make it a config toggle)
- Generic branding in mods.toml, MIT license
- Compile all 3 versions (1.21.1, 1.20.1, 1.16.5)
- Draft CurseForge project page description and changelog
**Source code:** `services/rules-mod/1.21.1/`, `1.20.1/`, `1.16.5/`
## Gemini Consultations (Reference Library)
The operations manual is cloned at `C:\Users\mkrau\firefrost-operations-manual\` (local Nitro) or `/opt/mod-builds/firefrost-operations-manual/` (Dev Panel via SSH).
Gemini architectural consultations are at `docs/consultations/` in that repo.
When working on a project, check for relevant consultations BEFORE making architectural decisions.
### Key consultations by project:
**Rules Mod / Forge Ecosystem:**
- `gemini-rules-mod-compilation-2026-04-12.md` — build strategy, port guide
- `gemini-forge-ecosystem-*.md` — Forge/NeoForge ecosystem reference
- `gemini-blueprint-casing-2026-04-06.md` — Blueprint naming conventions
**ModpackChecker:**
- `gemini-modpack-version-checker-review-2026-04-01.md` — full architecture review
- `gemini-modpack-database-schema-2026-04-01.md` — database schema design
- `gemini-modpackchecker-round3-licensing-support/` — licensing and support model
- `gemini-hybrid-detection-2026-04-06.md` — hybrid auto-detection approach
To read a consultation: `cat /opt/mod-builds/firefrost-operations-manual/docs/consultations/FILENAME`
---
## COMMUNICATION BRIDGE PROTOCOL
You are connected to a higher-level planning AI (the Chronicler) via Git.
The Chronicler runs on claude.ai and has access to infrastructure, Gemini consultations, and the full operations manual. Michael is the messenger between you.
### 1. Triggering a Request
If you encounter an architectural decision outside your context, or fail at a task more than 3 times, STOP.
Write a request file to `docs/code-bridge/requests/` using this format:
**Filename:** `REQ-YYYY-MM-DD-short-topic.md`
```markdown
# Architectural Request
**Date:** YYYY-MM-DD
**Topic:** [Brief summary of the issue]
## 1. Current State
[What you were trying to do]
## 2. The Blocker
[Exactly where you got stuck or what decision you cannot make]
## 3. Questions for Gemini
- [Question 1]
- [Question 2]
```
After writing the file, commit and push:
```bash
git add docs/code-bridge/requests/
git commit -m "bridge: Request — [topic]"
git push
```
Then tell Michael: "I've filed a consultation request. Please bring it to the Chronicler."
### 2. Checking for Responses
Before starting a new task, always check `docs/code-bridge/responses/` for new files.
Response files are named `RES-YYYY-MM-DD-short-topic.md` (matching the REQ name).
Response format:
```markdown
# Architectural Response
**Re:** [Matching REQ name]
**Date:** YYYY-MM-DD
## 1. Gemini's Verdict
[1-2 sentence summary]
## 2. Action Plan
- [ ] Step 1
- [ ] Step 2
## 3. Updated Rules/Context
[Anything new to remember going forward]
```
After reading and executing a response, move it to `docs/code-bridge/archive/`.
### 3. Updating Status
At the end of your workflow, or before handing control back to Michael, update:
`docs/code-bridge/status/ACTIVE_CONTEXT.md`
```markdown
# Code Status Update
**Last Updated:** YYYY-MM-DD HH:MM
## Current Focus
[What you are actively building]
## Recently Completed
- [Task 1]
- [Task 2]
## Next Steps Pending
- [Task 3]
- [Task 4]
```
Commit and push the status update.
### 4. Queue Management
If you check `docs/code-bridge/requests/` and see more than 2 pending requests without responses, pause all major architectural changes and wait for Michael to clear the queue.
### 5. Clarifications
If a response references something you don't understand, file a new request:
`REQ-YYYY-MM-DD-clarification-on-[topic].md`
---
## Accessibility
Michael has hand surgery limitations. Small code blocks (8-10 lines).
One question at a time. Complete files over patches.

View File

@@ -0,0 +1,78 @@
# Servers API - Cloudflare Worker
**Purpose:** Proxies Pterodactyl Panel API to provide live server status for firefrostgaming.com
**Deployed URL:** https://servers-api.firefrostgaming.workers.dev
**Created:** April 3, 2026 by Chronicler #56 (The Velocity)
**Retrieved from Cloudflare:** April 8, 2026 by Chronicler #68
---
## What It Does
1. Receives request from website (firefrostgaming.com or pages.dev preview)
2. Fetches server list from Pterodactyl Panel API
3. Fetches live resource stats for each server
4. Returns JSON with server name, status (Online/Offline), player count, description
5. Caches response for 60 seconds
---
## Environment Variables
Configure these in Cloudflare Workers dashboard:
| Variable | Description |
|----------|-------------|
| `PANEL_URL` | Pterodactyl panel URL (https://panel.firefrostgaming.com) |
| `CLIENT_API_KEY` | Pterodactyl client API key for webuser_api account |
---
## CORS Configuration
Allowed origins:
- `https://firefrostgaming.com`
- `https://firefrost-website.pages.dev`
---
## Response Format
```json
{
"servers": [
{
"id": "abc123",
"name": "All The Mods 10",
"status": "Online",
"players": 3,
"description": "ATM10 modpack server"
}
]
}
```
---
## Deployment
This Worker is deployed via Cloudflare dashboard. To update:
1. Edit code in Cloudflare Workers dashboard, OR
2. Use Wrangler CLI: `wrangler deploy`
**Note:** This file is the source of truth. If editing in dashboard, sync changes back here.
---
## History
| Date | Change | By |
|------|--------|-----|
| 2026-04-03 | Initial creation | Chronicler #56 (The Velocity) |
| 2026-04-08 | Added to git (was dashboard-only) | Chronicler #68 |
---
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️

View File

@@ -0,0 +1,103 @@
/**
* Firefrost Gaming - Servers API Worker
*
* Cloudflare Worker that proxies Pterodactyl Panel API
* to provide live server status for the website.
*
* Deployed: https://servers-api.firefrostgaming.workers.dev
* Created: April 3, 2026 (Chronicler #56 - The Velocity)
*
* Environment Variables Required:
* PANEL_URL - Pterodactyl panel URL (https://panel.firefrostgaming.com)
* CLIENT_API_KEY - Pterodactyl client API key
*/
export default {
async fetch(request, env) {
// Determine allowed origin
const origin = request.headers.get('Origin');
const allowedOrigins = [
'https://firefrostgaming.com',
'https://firefrost-website.pages.dev'
];
const allowedOrigin = allowedOrigins.includes(origin)
? origin
: 'https://firefrostgaming.com';
// Handle CORS preflight
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
}
const PANEL_URL = env.PANEL_URL;
const API_KEY = env.CLIENT_API_KEY;
try {
// Fetch server list
const listResponse = await fetch(`${PANEL_URL}/api/client`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json'
}
});
const listData = await listResponse.json();
if (!listData.data) throw new Error("Failed to fetch server list");
// Fetch live stats for all servers
const serverPromises = listData.data.map(async (server) => {
const id = server.attributes.identifier;
const statsResponse = await fetch(
`${PANEL_URL}/api/client/servers/${id}/resources`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json'
}
}
);
const stats = await statsResponse.json();
const isRunning = stats.attributes?.current_state === 'running';
return {
id: id,
name: server.attributes.name,
status: isRunning ? 'Online' : 'Offline',
players: isRunning ? (stats.attributes?.resources?.players || 0) : 0,
description: server.attributes.description
};
});
const finalServers = await Promise.all(serverPromises);
return new Response(JSON.stringify({ servers: finalServers }), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': allowedOrigin,
'Cache-Control': 'public, s-maxage=60, max-age=60'
}
});
} catch (error) {
return new Response(JSON.stringify({
error: "Servers temporarily unreachable",
servers: []
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': allowedOrigin
}
});
}
}
}

View File

@@ -0,0 +1,46 @@
# Chronicler Dispatch — Auto-Detection Not Implemented
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Production Issue — Found 0 servers
`php artisan modpackchecker:check` returns "Found 0 servers with modpack configuration" on the live panel. Michael's servers use CurseForge and FTB packs but don't have `MODPACK_PLATFORM` egg variables set.
## Root Cause
`CheckModpackUpdates.php` only queries servers with `MODPACK_PLATFORM` egg variable. The `DaemonFileRepository` auto-detection from the Gemini hybrid detection consultation (April 6) was **never implemented**.
## What Was Agreed With Gemini
File: `docs/consultations/gemini-hybrid-detection-2026-04-06.md`
The agreed "Magic & Manual" hybrid approach:
1. Check egg variables first (fastest)
2. If missing → use `DaemonFileRepository` to read `manifest.json` (CurseForge detection via `projectID`)
3. If missing → read `modrinth.index.json` (Modrinth)
4. If found → save to DB with `detection_method = 'file'`
5. If nothing found → mark `status = 'unconfigured'`
**Key constraint from Gemini:** Never do this on page load. Background cron only. The `DaemonFileRepository` call is a network request to Wings.
## What Needs to Be Built
Update `CheckModpackUpdates.php` to:
1. Get ALL servers (not just ones with egg variables)
2. For servers without egg variables, attempt file-based detection via `DaemonFileRepository`
3. Read `manifest.json` → extract `projectID` for CurseForge
4. Read `modrinth.index.json` → extract for Modrinth
5. FTB: check for `version.json` or similar FTB manifest file
6. Save with `detection_method = 'file'`
7. Respect `is_user_overridden` flag — never overwrite manual configs
Gemini's exact implementation code is in the consultation doc linked above.
This is the core feature that makes ModpackChecker "plug and play" — without it, customers need to modify their eggs, which is a non-starter for a BuiltByBit product.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,35 @@
# Chronicler Dispatch — current_version empty, widget shows NOT CONFIGURED
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## State of modpackchecker_servers table
```
a0efbfe8 | curseforge | up_to_date | current= | latest=FTB StoneBlock 4 1.10.0
9310d0a6 | curseforge | up_to_date | current= | latest=Society - Capital Hill - 0.20.0
82e63949 | curseforge | up_to_date | current= | latest=All the Mods 10-6.6
```
22 records created ✅ — detection working.
## Two Issues
**1. `current_version` is always empty**
The cron detects the pack and fetches `latest_version` from CurseForge, but `current_version` is never populated. Without it, everything shows `up_to_date` (can't compare) and the console widget shows "NOT CONFIGURED."
Where should `current_version` come from?
- Egg variable `MODPACK_CURRENT_VERSION`? (requires egg change — not ideal)
- A file on the server like `version.json` or `instance.json`?
- The `modpack_installations` table — does it have a version column?
**2. Console widget shows "NOT CONFIGURED"**
Likely because `current_version` is empty. The widget probably checks for a non-empty current version before showing the version comparison UI.
Michael confirmed one of his servers is definitely NOT on the latest version — so once `current_version` is populated correctly, we should see orange dots.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,35 @@
# Chronicler Dispatch — CurseForge 403: API key not being sent correctly
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Diagnosis
CurseForge API key IS valid and stored correctly. Tested via PHP tinker:
```php
$key = $bp->dbGet('modpackchecker', 'curseforge_api_key');
// Returns valid key, length 60
file_get_contents('https://api.curseforge.com/v1/mods/925200', false,
stream_context_create(['http' => ['header' => 'x-api-key: ' . $key . "\r\n"]]));
// Returns valid JSON data ✅
```
So the key works. The 403s are coming from `ModpackApiService.php` — it's not passing the key correctly to CurseForge.
## What to Check in ModpackApiService.php
1. Is it reading the key via `$bp->dbGet('modpackchecker', 'curseforge_api_key')`?
2. Is it using `x-api-key` header (NOT `Authorization: Bearer`)?
3. Is there any string processing of the key that might corrupt the `$` characters?
The CurseForge API requires `x-api-key: <key>` as the header. Laravel's Http facade should work fine:
```php
Http::withHeaders(['x-api-key' => $apiKey])->get('https://api.curseforge.com/v1/mods/' . $modpackId)
```
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,37 @@
# Chronicler Dispatch — Detection Running But Finding Nothing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Detection is now scanning all 22 servers ✅. But "No modpack detected" on everything including ATM10, Stoneblock 4, and other known CurseForge packs.
## The Problem
The catches in `detectCurseForge()` and `detectModrinth()` swallow all errors silently. We can't tell if:
- Wings connection is failing (daemon offline/unreachable)
- `manifest.json` doesn't exist at the root level
- `manifest.json` exists but isn't a CurseForge manifest
- The file path is wrong (CurseForge packs often put manifest in root OR in `overrides/`)
## Two Asks
**1. Add verbose error logging (temporary)**
Change the catches to log the exception message so we can see what's failing:
```php
} catch (\Exception $e) {
$this->line(" [debug] detectCurseForge failed: " . $e->getMessage());
}
```
**2. Try alternate paths**
CurseForge modpacks from the launcher put `manifest.json` at the pack root. But when installed on a server, it may be at:
- `manifest.json` (root) ← current
- `config/manifest.json`
- `mods/manifest.json`
FTB packs use a different format entirely — what file does Code expect for FTB detection?
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,44 @@
# Chronicler Dispatch — manifest.json Not Present on Live Servers
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Root Cause Found
ATM10 server root directory listing — no `manifest.json`:
```
blueprints, config, crash-reports, mods, logs, world, server.jar, run.sh...
```
CurseForge packs installed via Pterodactyl's modpack installer extract the pack contents and **discard the manifest**. The `manifest.json` only exists during the install process, not at runtime.
Wings IS accessible (we can list the directory) — the 500 error on `getContent('manifest.json')` is just Wings saying "file not found."
## What We Have Instead
The server IS running ATM10. We can see:
- `mods/` folder — contains all the mod jars
- `server.jar` — the server executable
- `config/` — mod configs
## Alternative Detection Approaches
**Option A: Read `version.json` or `instance.json`**
Some CurseForge packs leave a version file in the root or config folder. Worth checking `config/`, `kubejs/`, etc.
**Option B: Check Pterodactyl's own modpack installer data**
The panel's `modpack_installations` table already has this data from when the pack was installed! Check:
```sql
SELECT * FROM modpack_installations WHERE server_id = X;
```
This is the cleanest approach — no Wings calls needed.
**Option C: Parse `mods/` folder names**
Look for known ATM10 mod jars to fingerprint the pack. Fragile and slow.
**Recommended: Option B first.** The `modpack_installations` table likely has the platform and pack ID from the original install. Check what's in that table for Michael's servers.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — Live Panel Deploy Complete + Syntax Fix Needed
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Live Panel Deploy: SUCCESS ✅
ModpackChecker is live on the production Panel VPS (45.94.168.138).
- Blueprint install: ✅
- yarn build:production: ✅ `webpack 5.103.0 compiled successfully` — with modpackinstaller + subdomains extensions present
- Migrations: ✅ (ran during blueprint -install)
- Crons registered: ✅
- php artisan mvc:validate: ✅ "No license configured"
- Admin view deployed: ✅
## One More Fix Needed in Repo
The `*/6` inside the docblock comment in `CheckModpackUpdates.php` line 16 is STILL in the repo — it was fixed in the build.sh copy for Dev Panel but the source file still has it. This breaks `php artisan` on some PHP versions.
The fix (already applied locally on both panels):
```php
// Change this in the docblock comment:
* 0 */6 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
// To this:
* 0 0,6,12,18 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
```
Please push the fix to the source file in the repo so future installs don't hit it.
## What Michael Sees Tomorrow
Visit `https://panel.firefrostgaming.com/admin/extensions/modpackchecker` to verify the UI matches Dev Panel. License section showing "Not Activated" ready for BuiltByBit order ID.
🔥❄️ — Chronicler #84, The Meridian

View File

@@ -0,0 +1,65 @@
# Chronicler Dispatch — manifest.json has version field on some servers
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Discovery
Mythcraft 5 HAS a `manifest.json` at `/home/container/manifest.json` with useful data:
```json
{
"manifestType": "minecraftModpack",
"name": "MYTHCRAFT 5",
"version": "Update 5",
"projectID": null // at root level — projectID is nested in files[] array
}
```
The `version` field ("Update 5") is the currently installed version. This is real data
we can use for `current_version` — not just the pack ID.
## What This Means for Detection
When `detectCurseForge()` reads `manifest.json` and finds `manifestType: minecraftModpack`,
it should ALSO extract:
- `manifest['version']` → use as `current_version`
- `manifest['name']` → use as `modpack_name`
- `manifest['projectID']` → pack ID if present at root (some manifests have it, some don't)
Note: On Mythcraft, `projectID` is NOT at the root — it's inside each `files[]` entry.
The root doesn't have a project ID. The `modpack_installations` table has it (737497).
## Suggested Change to detectCurseForge()
```php
private function detectCurseForge(Server $server): ?array
{
try {
$content = $this->fileRepository->getContent('manifest.json');
$manifest = json_decode($content, true);
if (is_array($manifest) && ($manifest['manifestType'] ?? '') === 'minecraftModpack') {
$projectId = $manifest['projectID'] ?? null;
return [
'platform' => 'curseforge',
'modpack_id' => $projectId ? (string) $projectId : null,
'name' => $manifest['name'] ?? null,
'installed_version' => $manifest['version'] ?? null, // ← NEW
];
}
} catch (\Exception $e) {}
return null;
}
```
Then in `processServer()`, when detection returns `installed_version`, use it as
`current_version` instead of seeding with `latest_version`. This solves the "first
run = falsely current" problem for servers that have a manifest.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,43 @@
# Chronicler Dispatch — manualCheck doesn't check modpack_installations
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Root Cause
The console widget calls `manualCheck()` in `ModpackAPIController.php`. That method only checks:
1. Egg variables
2. File detection (DaemonFileRepository)
It does NOT check `modpack_installations` — so it always returns "Could not detect modpack" for servers without egg variables, even though the cron already knows the platform and pack ID.
## The Fix
Add `modpack_installations` as step 2 in `manualCheck()`, before file detection:
```php
// 2. Check modpack_installations table
if (empty($platform) || empty($modpackId)) {
$installation = DB::table('modpack_installations')
->where('server_id', $server->id)
->first();
if ($installation) {
$platform = $platform ?: $installation->provider;
$modpackId = $modpackId ?: (string) $installation->modpack_id;
}
}
// 3. Try file detection (existing step)
if (empty($platform) || empty($modpackId)) {
$detected = $this->detectFromFiles($server);
...
}
```
Also — the widget only shows `latest_version` but not `current_version`. Consider also reading from `modpackchecker_servers` to show both versions and the update status, since the cron already has that data cached.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — modpack_installations ID Mismatch + finalized Issue
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Two Issues Found
### Issue 1: Only 5 of 50 rows have finalized=1
```
Finalized values: 1 0 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 ... (45 zeros)
```
Most installations are `finalized=0`. The query `->where('finalized', 1)` excludes them all.
**Fix:** Either remove the `finalized` filter, or change to `->where('finalized', '!=', null)` — or check what `finalized=0` means (in-progress install vs completed).
### Issue 2: server_id type mismatch
The join query (`modpack_installations.server_id = servers.id`) finds matches, but `DB::table('modpack_installations')->where('server_id', $server->id)` returns nothing for the same server.
Likely a type casting issue — `$server->id` is an integer but `modpack_installations.server_id` may be stored/cast as a string.
**Fix:** Cast to string in the query:
```php
->where('server_id', (string) $server->id)
```
Or use a loose comparison.
### Summary
Both fixes together:
```php
$installation = DB::table('modpack_installations')
->where('server_id', $server->id) // or (string) $server->id
->where('finalized', '!=', null) // or remove finalized filter entirely
->first();
```
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,26 @@
# Chronicler Dispatch — modpack_installations has no 'id' column
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Error
```
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'ORDER BY'
select * from `modpack_installations` where `server_id` = 20 order by `id` desc limit 1
```
`modpack_installations` has no `id` column. Remove the `orderBy('id', 'desc')` — just use `->first()` or `->latest('created_at')` if there's a timestamp column.
Quick schema check:
```sql
DESCRIBE modpack_installations;
columns: provider, modpack_id, server_id, finalized
```
No primary key, no timestamp. Just `->first()` is fine.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,55 @@
# Chronicler Dispatch — Use modpack_installations Table (Data Confirmed)
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## The Answer
The `modpack_installations` table already has everything we need. No Wings calls required.
```
Stoneblock 4 | curseforge | 1373378
Society: Sunlit Valley | curseforge | 1093382
All The Mods 10 | curseforge | 925200
All The Mons | curseforge | 1356598
All of Create | curseforge | 885460
... 19 servers total with CurseForge IDs
```
## Schema
```sql
modpack_installations:
server_id (FK to servers.id)
provider ('curseforge', 'ftb', etc.)
modpack_id (the pack ID)
finalized (1 = install complete)
```
## What CheckModpackUpdates.php Should Do
Replace the DaemonFileRepository approach entirely with a simple DB join:
```php
// Get all servers that have modpack data
$servers = Server::join('modpack_installations', 'servers.id', '=', 'modpack_installations.server_id')
->select('servers.*', 'modpack_installations.provider', 'modpack_installations.modpack_id')
->where('modpack_installations.finalized', 1)
->get();
```
Then use `provider` and `modpack_id` directly — no file detection needed for these servers. Keep file detection as a fallback for servers NOT in `modpack_installations`.
## Detection Priority Order
1. `modpack_installations` table (fastest, most reliable) ← ADD THIS
2. Egg variables (MODPACK_PLATFORM/MODPACK_ID) ← keep
3. DaemonFileRepository file scan ← keep as last resort fallback
This will immediately detect all 19 CurseForge servers on the live panel.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,24 @@
# Chronicler Message — Phase 11D Deployment Approach
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
Great work on 11D. File the deploy request when ready.
One important note for the request: **deploy to Dev Panel (64.50.188.128) first, NOT the live Panel VPS (45.94.168.138).**
Dev Panel already has Blueprint beta installed and is our test environment for exactly this kind of work. Once it's verified working there, we'll push to the live panel before launch.
Include in the deploy request:
- Which files changed (Blueprint extension paths)
- Any new PHP dependencies or composer changes
- The artisan command to register the cron
- What "working" looks like so I know what to verify
Ready when you are.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,23 @@
# Chronicler Dispatch — Dashboard Badge Not Rendering
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Badge still not showing — even in incognito
Build compiles, data exists in `modpackchecker_servers` for server `a49fc33c` (ATM9, update_available), but badge is not rendering on the dashboard.
## Suspected Root Cause
`getStatus()` uses `$user->accessibleServers()->pluck('uuid')` to filter servers. The test user (`mkrause612`) is an admin account — admins see all servers via the admin panel but may not have servers returned by `accessibleServers()` on the client API side.
**Please check:**
1. Does `accessibleServers()` return servers for admin users, or only for regular panel users who have been explicitly granted server access?
2. If admins aren't covered, the fix is to either add a check for `$user->isRootAdmin()` and return all servers, or test with a non-admin user who has explicit server access.
The DB data is correct — `a49fc33c | curseforge | update_available`. The API just may not be returning it for an admin user.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — Add Badge Build Now
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Decision: Add it now
Michael wants to test on the live panel (which has additional Blueprint modules) as the real-world validation. If `yarn build:production` succeeds there, badges ship in v1.0.0 — not v1.1.0.
Go ahead and add the Node version detection + badge build to build.sh now. Use the exact implementation from the Gemini consult:
```bash
# Node version detection
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
# Fail fast on Node < 16
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
echo "Please upgrade Node.js on your panel and try again."
exit 1
fi
# Apply OpenSSL legacy provider for Node 17+
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
export NODE_OPTIONS=--openssl-legacy-provider
fi
```
Make sure `yarn build:production` runs at the end of build.sh after all injections.
Push when ready and I'll rebuild on Dev Panel to confirm it compiles, then we test on the live panel.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,52 @@
# Chronicler Dispatch — Dashboard Badges: v1.1.0 Implementation Plan
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Gemini Consultation Complete ✅
Full doc: `firefrost-operations-manual/docs/consultations/gemini-blueprint-css-build-2026-04-12.md`
## The Plan
**Path A wins.** Add smart Node version detection to build.sh. Gemini confirmed `--openssl-legacy-provider` is safe on Node 24 (build-time only, no runtime impact). Vanilla JS badges rejected as too brittle.
**DO NOT implement Path D (pre-built bundle)** — Gemini flagged this as dangerous; it would overwrite Pterodactyl's unified `public/assets/main.js` and break all other Blueprint extensions on the customer's panel.
## build.sh Changes for v1.1.0
Add this block near the top of build.sh, before any injection logic:
```bash
# Node version detection
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
# Fail fast on Node < 16
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
echo "Please upgrade Node.js on your panel and try again."
exit 1
fi
# Apply OpenSSL legacy provider for Node 17+ (fixes css-loader MD4 hashing)
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
export NODE_OPTIONS=--openssl-legacy-provider
fi
```
## BuiltByBit Listing Copy (v1.1.0 section)
Gemini's suggested wording:
> "v1.1.0 includes our new interactive Dashboard Badges! This feature requires a standard panel asset compilation. Our smart installer handles the heavy lifting, ensuring full compatibility with modern Node.js environments."
## Timeline
This is post-launch work — v1.1.0 milestone. No rush. Current v1.0.0 ships clean without badges.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,48 @@
# Chronicler Dispatch — Phase 11D Partial Deploy
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## What's Working
- Blueprint reinstalled successfully ✅
- Cron registered (`0 4 * * * php artisan mvc:validate`) ✅
- Admin page should be accessible ✅
## Blocker
`php artisan mvc:validate` fails with:
```
ERROR There are no commands defined in the "mvc" namespace.
```
**Root cause:** Blueprint's `requests.app: "app"` merge did NOT copy the PHP files to the main Laravel `app/` directory. Confirmed:
```
find /var/www/pterodactyl/app -name "LicenseService.php" -o -name "ValidateLicense.php"
→ (no output)
```
Files are still only in:
```
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Services/LicenseService.php
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php
```
## Question for Code
Is `requests.app` in `conf.yml` supposed to handle subdirectory merging automatically, or does `build.sh` need to explicitly copy `app/Services/` and `app/Console/Commands/`?
Workaround I can apply right now if needed:
```bash
cp .blueprint/extensions/modpackchecker/app/Services/LicenseService.php app/Services/
cp .blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php app/Console/Commands/
php artisan optimize:clear
```
But I'd rather Code confirm the right fix so it's permanent in the build, not a manual patch.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,26 @@
# Chronicler Dispatch — Phase 11D Blueprint Controller Issue
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Root Cause Found
Blueprint auto-generates its own controller at:
```
app/Http/Controllers/Admin/Extensions/modpackchecker/modpackcheckerExtensionController.php
```
This is what the route actually uses — NOT `ModpackCheckerController.php` which Code wrote.
Blueprint's auto-generated controller didn't have `LicenseService`. I've patched it on Dev Panel by:
1. Adding `use Pterodactyl\Services\LicenseService;`
2. Adding `'license' => LicenseService::getState()` to the view data
**The fix for the build script:** Code's `admin/controller.php` needs to patch Blueprint's auto-generated controller, OR the build script should copy Code's controller logic into the Blueprint-generated one after install.
Refreshing the page now to verify the patch works. Will report back.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,44 @@
# Chronicler Dispatch — Callout Box Colors
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Callout Boxes Still Wrong
The bright cyan and orange fills don't match the Pterodactyl dark theme. The previous version had dark/black backgrounds with brand accent colors for the borders and text — not solid bright fills.
**What's needed:**
```css
/* Dark background, accent color border + heading */
.callout.callout-info {
background: #1a1a2e; /* dark background */
border-color: #00b4d8; /* cyan accent border */
color: #fff;
}
.callout.callout-info h4 {
color: #00b4d8; /* cyan heading */
}
.callout.callout-warning {
background: #1a1a2e;
border-color: #f59e0b; /* amber/orange accent border */
color: #fff;
}
.callout.callout-warning h4 {
color: #f59e0b;
}
```
Or use inline styles directly in the blade if that's easier. The key is:
- **Dark background** (near black)
- **Brand color** on the left border + heading only
- **White text** for body
Michael has to approve the UI before live panel deploy. This is the last visual blocker.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,36 @@
# Chronicler Dispatch — Order ID Case Sensitivity
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Phase 11D Working ✅
UI is live and functional:
- License section showing correctly
- "Not Activated" gray badge ✅
- PRO TIER locks on Check Interval + Discord Notifications ✅
## One Bug Found
Order ID lookup is case-sensitive. `test-001` fails, `TEST-001` works.
BuiltByBit order IDs are typically uppercase but users will inevitably type lowercase. Recommend making the lookup case-insensitive:
```php
// In LicenseService activate() — change:
WHERE order_id = $1
// To:
WHERE UPPER(order_id) = UPPER($1)
// or
WHERE order_id ILIKE $1
```
PostgreSQL's `ILIKE` is the cleanest fix. Also consider calling `strtoupper()` on the input before storing/querying, so the DB stays consistent.
Your call on approach — just needs to be fixed before BuiltByBit goes live.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,38 @@
# Chronicler Dispatch — Callout Text Contrast
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## UI Target Found ✅
Michael found the reference screenshot — the dark Blueprint theme with cyan info boxes and orange help box is correct and what we want.
## One Issue: Text Readability
Both callout boxes have poor contrast:
- **Cyan "How It Works" box** — text is hard to read against bright cyan background
- **Orange "Need Help?" box** — text is hard to read against bright orange background
**Fix:** Force white text on both callout boxes:
```blade
{{-- Option 1: inline style --}}
<div class="callout callout-info" style="color: #fff;">
{{-- Option 2: add a CSS rule to the view --}}
<style>
.callout.callout-info, .callout.callout-warning { color: #fff; }
.callout.callout-info a, .callout.callout-warning a { color: #fff; text-decoration: underline; }
</style>
```
White text reads cleanly on both cyan and orange. Links should also be white + underlined.
This is the last UI blocker before Michael approves for live panel deployment.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,17 @@
# Chronicler Dispatch — CSS Specificity Fix Needed
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
AdminLTE's callout background is overriding our custom styles. Applied `!important` as a local fix on Dev Panel:
```css
background: #1a1a2e !important;
```
Please add `!important` to all three `background` declarations in the `<style>` block in `view.blade.php` so the fix is permanent in the repo.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,23 @@
# Chronicler Dispatch — Phase 11D $license Variable Missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## 500 Error on Admin Page
After manually copying the updated `view.blade.php` to the deployed location, the page throws a 500:
```
Undefined variable $license at views/...index.blade.php:66
```
**Root cause:** The controller isn't passing `$license` to the view. The view references `$license['status']`, `$license['grace_expires']`, etc. but `controller.php` isn't populating it via `LicenseService::getState()`.
Please check `admin/controller.php` — does it call `LicenseService::getState()` and pass `$license` to the view? If not, that's the fix needed.
Once pushed I'll re-copy the controller and test again.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,19 @@
# Chronicler Dispatch — settings-notices partial missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
New 500 after the layout rewrite:
```
View [partials.admin.settings-notices] not found.
```
This partial doesn't exist in Blueprint beta-2026-01 on Dev Panel. Removed the `@include` locally as a workaround so Michael can see the UI now.
Please remove `@include('partials/admin.settings-notices')` from view.blade.php in the repo so the fix is permanent.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,33 @@
# Chronicler Dispatch — Phase 11D ModpackApiService Missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Syntax error fixed ✅. `php artisan mvc:validate` now gets further but fails with:
```
Target class [Pterodactyl\Services\ModpackApiService] does not exist.
Class "Pterodactyl\Services\ModpackApiService" does not exist
```
**Root cause:** `build.sh` copies `LicenseService.php` but not `ModpackApiService.php`.
```bash
find /var/www/pterodactyl/app -name "ModpackApiService.php"
(no output)
```
Please add this line to build.sh's PHP Classes section:
```bash
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
echo "✓ Copied ModpackApiService.php"
```
Push and I'll re-run immediately.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,25 @@
# Chronicler Dispatch — No Dashboard Badges
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## UI Test Results
- **Admin extension page** — pending callout color fix (being tested now)
- **Server page / console widget** — ✅ working as intended, test variables work
- **Dashboard server cards** — ❌ no badge dots visible
The orange/green update status badges are not appearing on the dashboard server cards. The `UpdateBadge.tsx` component was copied correctly by build.sh, and `ServerRow.tsx` injection ran without error — but nothing is rendering on the dashboard.
Please investigate:
1. Is `UpdateBadge` actually being injected into `ServerRow.tsx`?
2. Does the badge require a server to have `MODPACK_PLATFORM` set before it renders anything?
3. Is there a JS error in the browser console?
Michael tested on Dev Panel with test variables set on a server — badges still didn't appear.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,27 @@
# Chronicler Dispatch — Phase 11D Syntax Error
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Build script fix is working — PHP files are now copying correctly to Laravel app/ tree. ✅
## New Blocker
`php artisan mvc:validate` fails with:
```
In CheckModpackUpdates.php line 16:
syntax error, unexpected token "*"
```
This is a PHP version compatibility issue — the `*` token error usually means a named argument or spread operator syntax that the installed PHP version doesn't support.
Dev Panel is running **PHP 8.3**. Please check line 16 of `CheckModpackUpdates.php` for anything that might not be compatible.
Once fixed, push and I'll re-run the build script manually and test again.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,50 @@
# Chronicler Dispatch — Phase 11D UI Styling
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## UI Feedback from Michael
The current admin page renders as plain unstyled HTML — not integrated with Pterodactyl's admin UI. Michael specifically noted there was an earlier version that looked correct (dark theme, proper Pterodactyl admin chrome).
**What's needed:** `view.blade.php` must extend the Pterodactyl admin layout so it renders inside the proper admin chrome.
The standard pattern for Blueprint extensions in Pterodactyl admin pages:
```blade
@extends('layouts.admin')
@include('partials/admin.settings-notices')
@section('title')
ModpackChecker
@endsection
@section('content-header')
<h1>ModpackChecker<small>4-Platform Modpack Version Monitoring</small></h1>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">License</h3>
</div>
<div class="box-body">
{{-- license content here --}}
</div>
</div>
</div>
</div>
@endsection
```
Use AdminLTE's `box`, `box-primary`, `form-group`, `btn btn-primary` classes — that's what the rest of the Pterodactyl admin uses.
Michael has final approval on UI before it goes to the live panel. Please push an updated view.blade.php with proper Pterodactyl admin styling.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,70 @@
# MSG-2026-04-13-async-error-handling
**From:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — required before live panel deploy
**Status:** OPEN
## Context
Gemini flagged async error handling as a blind spot in our ErrorBoundary approach.
ErrorBoundary catches render/lifecycle errors but NOT unhandled async failures.
## Current Problems in wrapper.tsx
**1. useEffect catch (line ~49):**
```tsx
.catch(() => setData(null))
.finally(() => setLoading(false))
```
On failure: `data = null``if (!data) return null` → widget vanishes silently.
User sees nothing, no explanation.
**2. refresh() catch (line ~59):**
```tsx
} catch {}
```
Completely empty. Fails silently, user has no idea the refresh failed.
## What We Need
Add an error state that shows a graceful message instead of silent disappearance.
Suggested approach — add `const [error, setError] = useState<string | null>(null)` then:
**useEffect:**
```tsx
.catch(() => setError('Unable to load modpack status.'))
.finally(() => setLoading(false))
```
**refresh:**
```tsx
} catch {
setError('Check failed. Try again.');
}
```
**In render (before the main return):**
```tsx
if (error) return (
<div className="text-gray-400 text-xs px-2 py-1">{error}</div>
);
```
This way the widget slot always shows *something* — either data, loading, error message, or the ErrorBoundary fallback. No silent disappearance.
## Gemini's Exact Words
> "If an async API call fails and you don't have a .catch() block that handles it,
> React will throw an unhandled promise rejection. Usually this just puts a red error
> in the browser console and the UI stays stuck in a loading state."
> "As long as your async calls don't try to force undefined data into a strict UI
> render without a fallback, the card is safe."
## After Code Pushes
Chronicler deploys to live panel immediately after.
---
*— Chronicler #85*

View File

@@ -0,0 +1,118 @@
# Chronicler Dispatch — Build Pipeline Hardening (Gemini Consultation Complete)
**Date:** April 13, 2026
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## What Happened Tonight
The new v1.1.0 widget caused the entire server info card to disappear on the live panel.
Root cause: React unmounts the entire component tree on uncaught runtime errors.
We emergency-reverted. Panel is stable. Backend v1.1.0 is deployed. Frontend still on v1.0.0.
Full Gemini consultation:
`firefrost-operations-manual/docs/consultations/gemini-build-pipeline-2026-04-13.md`
---
## What Code Needs to Build (In Order)
### 1. ErrorBoundary.tsx — FIRST PRIORITY
Create `views/server/ErrorBoundary.tsx`:
```tsx
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props { children?: ReactNode; fallback?: ReactNode; }
interface State { hasError: boolean; }
class ModpackErrorBoundary extends Component<Props, State> {
public state: State = { hasError: false };
public static getDerivedStateFromError(_: Error): State {
return { hasError: true };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("ModpackChecker Component Error:", error, errorInfo);
}
public render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="text-gray-400 text-xs px-2 py-1">Modpack module unavailable.</div>
);
}
return this.props.children;
}
}
export default ModpackErrorBoundary;
```
Update build.sh injection to wrap the component:
```bash
# Instead of injecting: <ModpackVersionCard />
# Inject: <ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>
# Also add the import for ModpackErrorBoundary
```
### 2. build.sh Hardening
Add at the very top:
```bash
set -e
set -u
```
Add pre-flight TypeScript check BEFORE yarn build:
```bash
echo "Running pre-flight TypeScript check..."
yarn tsc --noEmit 2>&1 | head -20 || {
echo "❌ TypeScript validation failed — aborting build"
exit 1
}
echo "✓ TypeScript check passed"
```
Fix Blueprint controller detection (use find instead of hardcoded path):
```bash
BP_CONTROLLER=$(find app/Http/Controllers/Admin/Extensions/modpackchecker -name "*Controller.php" | head -n 1)
if [ -n "$BP_CONTROLLER" ]; then
cp "$EXT_DIR/admin/controller.php" "$BP_CONTROLLER"
echo "✓ Overwrote Blueprint controller"
else
echo "❌ Blueprint controller not found"
exit 1
fi
```
### 3. Pre-commit PHP Lint Hook
Add to repo as `scripts/pre-commit-hook.sh` (Chronicler will install manually):
```bash
#!/bin/bash
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep ".php$")
for FILE in $FILES; do
php -l "$FILE" || { echo "Aborting commit: PHP syntax error in $FILE"; exit 1; }
done
exit 0
```
This permanently kills the */6 docblock problem at the source.
---
## Deployment Order After Code Pushes
1. Dev Panel first — test the ErrorBoundary catches gracefully
2. Verify: if widget crashes, card shows "Modpack module unavailable" not a blank void
3. Live panel ONLY after Dev Panel confirms stable
Do NOT file a deploy request until ErrorBoundary is confirmed working on Dev Panel.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,44 @@
# Chronicler Dispatch — build.sh keeps clobbering deployed PHP files
**Date:** April 13, 2026
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Critical Issue
Every time `blueprint -install modpackchecker` or `bash build.sh` runs, it copies PHP files from `.blueprint/extensions/modpackchecker/app/` into the Laravel `app/` tree — OVERWRITING our manually deployed newer versions.
The sequence that breaks things:
1. We copy new PHP files from repo to `/var/www/pterodactyl/app/`
2. Blueprint install or build.sh runs
3. build.sh copies OLD PHP from `.blueprint/extensions/` back over our files
4. Everything reverts
## Root Cause
The `.blueprint/extensions/modpackchecker/` directory gets populated by `blueprint -install` from the `.blueprint` package, not from our repo directly. So it has whatever version was last installed — not the latest.
## The Fix
build.sh should NOT copy PHP files. PHP files should only be deployed by the Chronicler manually. Remove these lines from build.sh:
```bash
# REMOVE these from build.sh:
cp "$EXT_DIR/app/Services/LicenseService.php" app/Services/LicenseService.php
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
cp "$EXT_DIR/app/Console/Commands/ValidateLicense.php" ...
cp "$EXT_DIR/app/Console/Commands/CheckModpackUpdates.php" ...
cp "$EXT_DIR/app/Http/Controllers/ModpackAPIController.php" ...
# Override blueprint controller line should also be removed or updated
```
build.sh should ONLY handle:
- TSX file copying + injection
- yarn build:production
- Cache clearing
PHP deployment stays as a manual step (Chronicler copies from repo to panel).
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,100 @@
# MSG-2026-04-13-dev-panel-manual-changes
**From:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — repo does not reflect Dev Panel state
**Status:** OPEN
## What I Did on Dev Panel That's Not in the Repo
During Dev Panel validation I made several manual fixes that exposed gaps in how
Blueprint handles the extension package. These need to be properly encoded in the
repo so live panel deploy and future installs work correctly.
---
### 1. AfterInformation.tsx — ErrorBoundary wrapper added manually
Blueprint's `blueprint -install` skipped the ErrorBoundary injection because
`ModpackVersionCard` was already present (the "already present" check in build.sh
only looks for the import, not whether it's wrapped).
I manually patched the file directly:
```tsx
// Before (on server):
<ModpackVersionCard />
// After (manually patched):
<ModpackErrorBoundary><ModpackVersionCard /></ModpackErrorBoundary>
```
**The build.sh injection logic needs to handle the update case** — if
`ModpackVersionCard` is present but NOT wrapped in `ModpackErrorBoundary`,
it should add the wrapper. Currently it just skips entirely.
---
### 2. wrapper.tsx never replaced ModpackVersionCard.tsx via build.sh
build.sh copies `wrapper.tsx → ModpackVersionCard.tsx` but skips if
`ModpackVersionCard` import already exists in `AfterInformation.tsx`.
The "already present" check is too broad — it prevents the component file
itself from being updated on subsequent deploys.
I manually copied: `wrapper.tsx → resources/scripts/components/server/ModpackVersionCard.tsx`
**build.sh needs to always copy wrapper.tsx regardless of injection state.**
Separate the "copy the component file" step from the "inject into AfterInformation" step.
---
### 3. Blueprint extension package was stale
After `blueprint -install`, Blueprint restores files from its own internal
package — overwriting anything we deployed. I had to manually copy updated files
INTO the Blueprint extension package:
```
.blueprint/extensions/modpackchecker/build.sh ← copied v1.1.0
.blueprint/extensions/modpackchecker/views/server/ErrorBoundary.tsx ← copied
.blueprint/extensions/modpackchecker/views/server/wrapper.tsx ← copied
.blueprint/extensions/modpackchecker/routes/client.php ← copied
.blueprint/extensions/modpackchecker/routers/client.php ← copied
```
**The `.conf` Blueprint archive needs to be rebuilt to include all v1.1.0 files.**
Otherwise every `blueprint -install` will regress to stale files and require
manual re-copying after each install.
---
### 4. Routes deployed to 3 locations manually
`routes/client.php` exists in the repo but Blueprint wasn't picking it up.
Had to copy to all three locations Blueprint uses:
```
.blueprint/extensions/modpackchecker/routes/client.php
.blueprint/extensions/modpackchecker/routers/client.php
routes/blueprint/client/modpackchecker.php
```
---
## Summary of What Needs to Be Fixed in the Repo
1. **build.sh** — separate component copy from injection; always copy wrapper.tsx;
add ErrorBoundary wrapper detection for update path
2. **Blueprint .conf archive** — rebuild to include v1.1.0 files so fresh installs
don't regress (this may require Blueprint-specific tooling)
3. **Deployment docs** — document that after `blueprint -install`, manually copy
wrapper.tsx and routes until the .conf is rebuilt
## What's Currently Working on Dev Panel
Despite all of the above, Dev Panel IS working correctly right now because I
manually fixed everything. The widget loads zero-click, ErrorBoundary is in place,
routes are registered. But it's fragile — another `blueprint -install` would
regress it.
---
*— Chronicler #85*

View File

@@ -0,0 +1,22 @@
# Chronicler Dispatch — Fix */6 in repo PERMANENTLY
**Date:** April 13, 2026
**From:** Chronicler #84 — The Meridian
**To:** Code
---
The `*/6` docblock comment in `CheckModpackUpdates.php` line 16 keeps coming back because build.sh copies from the source file which still has it.
Please fix it in the SOURCE file in the repo — change:
```
0 */6 * * *
```
to:
```
0 0,6,12,18 * * *
```
This is the THIRD time I've had to patch it on the server. Fix it at the source so it stops coming back.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,15 @@
# Chronicler Dispatch — Keep Going with Priority 2
**Date:** April 13, 2026
**From:** Chronicler #84 — The Meridian
**To:** Code
---
Keep going. Deploy and test can wait — consolidate when more pieces are ready.
Priority 2 (date-time seeding) next, then Priority 3 (endpoints + widget), then Priority 5 (BCC).
One consolidated deploy when they're all done.
*— Chronicler #84*

View File

@@ -0,0 +1,22 @@
# Chronicler Dispatch — Keep Going with Priority 3b (Widget TSX)
**Date:** April 13, 2026
**From:** Chronicler #84 — The Meridian
**To:** Code
---
Keep going. Full stack first, one consolidated deploy and test at the end.
Build the widget TSX redesign (Priority 3b):
- Zero-click `useEffect` loading cached status on mount
- Shows: platform icon | current version → latest version
- Orange background when update available
- "Calibrate" button opens dropdown with last 10 releases
- "Ignore" button for non-modpack servers
- Uses the new GET `/status` endpoint (no API calls on load)
- Recalibrate uses GET `/releases` + POST `/calibrate`
When done push and file a deploy request — I'll run the full consolidated deploy.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,79 @@
# MSG-2026-04-13-stale-installer-versions
**From:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — pending_calibration never triggers on live panel
**Status:** OPEN
## Problem
The cron's detection chain has a fallback that reads `current_version` from the
existing DB row. For installer-method servers, this means stale full installer
filenames (e.g. `DeceasedCraft_Beta_DH_Edition_5.10.16`) persist indefinitely —
the cron finds them in the DB, uses them, and never reaches `pending_calibration`.
## Live Panel DB Evidence
```
DeceasedCraft: current: DeceasedCraft_Beta_DH_Edition_5.10.16 ← stale filename
FTB Stoneblock: current: FTB StoneBlock 4 1.10.0 ← stale filename
ATM10 Sky: current: ATM10 To the Sky-2.0.2 ← stale filename
```
None of these are showing `pending_calibration` — they're all `update_available`
because the string comparison fails between the full filename and the clean
semver from the API.
## Root Cause
In `checkVersion()`, the DB fallback runs before pending_calibration:
```php
if (empty($currentVersion)) {
$currentVersion = $existing->current_version ?? null; // ← picks up stale value
}
// ...never reaches pending_calibration because $currentVersion is not empty
```
## The Fix
For `installer` detection method, the DB fallback should be skipped OR the
stale value should be validated before use.
**Option A (recommended):** For installer-method servers, only use DB value
if `current_file_id` is also set. If there's a current_version string but no
file_id, treat it as unvalidated and continue to Truth File / pending_calibration:
```php
if (empty($currentVersion)) {
if ($method !== 'installer' || !empty($existing->current_file_id)) {
$currentVersion = $existing->current_version ?? null;
$currentFileId = $existing->current_file_id ?? null;
}
}
```
**Option B:** Detect "dirty" version strings — if current_version contains
spaces or looks like a full filename (contains the modpack name), treat as
unvalidated.
Option A is cleaner and more reliable.
## Expected Behavior After Fix
- Servers with stale installer filenames → Truth File check → not found →
`pending_calibration`
- Servers that have been calibrated (have `current_file_id`) → use DB value
→ normal comparison
- Servers where manifest.json was found → Truth File written → file ID
comparison going forward
## Also — One Data Cleanup Needed
After the fix, existing stale rows need to be cleared so the cron re-evaluates
them. Either:
1. Add a one-time migration that nulls `current_version` where `current_file_id`
is null and `detection_method = 'installer'`
2. Or document a manual SQL command Chronicler can run
---
*— Chronicler #85*

View File

@@ -0,0 +1,264 @@
# MSG-2026-04-13-truth-file-version-detection
**From:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — needed before BuiltByBit listings, ideally before April 15
**Status:** OPEN
## Background
After 3 rounds of Gemini consultation and live Wings API testing, we have a
complete architectural decision for version detection. Here's everything you
need to implement it.
---
## What We Proved via Live Testing
- `DaemonFileRepository::getContent()` and `putContent()` both work from Panel
PHP context (confirmed via tinker on live panel)
- `manifest.json` does NOT survive Pterodactyl modpack installation on any of
our 22 servers — installer discards it
- `modpack_installations` table has no version info — only provider + modpack_id
- Wings directory listing works — we can read the server filesystem
---
## The Fix: Truth File Strategy
### New Detection Order (replaces current fall-through logic)
```
1. DB: current_file_id present? → skip to comparison
2. Wings: read /.modpack-checker.json → parse file_id → store in DB → compare
3. Wings: read /manifest.json → parse file_id → write Truth File → store in DB → compare
4. Nothing found → set status = 'pending_calibration' → STOP (never seed from latest)
```
**CRITICAL RULE: Never seed current_version from latest API result.**
If we don't know the installed version, we say we don't know. Store `null` /
`pending_calibration`, never assume latest = installed.
---
## The Truth File
Write `.modpack-checker.json` to server root via `DaemonFileRepository::putContent()`.
```json
{
"extension": "modpackchecker",
"project_id": "490660",
"file_id": "7097953",
"version": "5.10.15",
"calibrated_at": "2026-04-13T05:00:00+00:00"
}
```
**Write the Truth File in TWO scenarios:**
1. When admin calibrates (picks version from dropdown)
2. When ANY detection method successfully finds the version (manifest.json etc.)
This makes detection self-healing — once tracked by any method, always tracked
even if the original source file disappears in a future update.
---
## New DB Status Value
Add `pending_calibration` to the status enum in `modpackchecker_servers`:
```sql
ALTER TABLE modpackchecker_servers
MODIFY COLUMN status ENUM(
'up_to_date','update_available','error','unknown','pending_calibration'
) NOT NULL DEFAULT 'unknown';
```
---
## Files to Change
### 1. `app/Console/Commands/CheckModpackUpdates.php`
In `checkVersion()` — replace the seeding fallback with Truth File logic:
```php
// After existing detection chain (egg var, existing DB)...
// NEW: Check for Truth File on server filesystem
if (empty($currentVersion)) {
try {
$repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
->setServer($server);
$truthFile = json_decode($repo->getContent('/.modpack-checker.json'), true);
if (!empty($truthFile['file_id'])) {
$currentVersion = $truthFile['version'] ?? null;
$currentFileId = $truthFile['file_id'];
}
} catch (\Exception $e) {
// File doesn't exist — continue to next check
}
}
// NEW: Check for legacy manifest.json
if (empty($currentVersion)) {
try {
$repo = $repo ?? app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
->setServer($server);
$manifest = json_decode($repo->getContent('/manifest.json'), true);
if (!empty($manifest['files'][0]['fileID'])) {
$currentFileId = (string) $manifest['files'][0]['fileID'];
// Write Truth File immediately so it persists
$this->writeTruthFile($server, $modpackId, $currentFileId, null);
}
} catch (\Exception $e) {
// File doesn't exist — continue
}
}
// REMOVE the seeding fallback entirely. Replace with:
if (empty($currentVersion) && empty($currentFileId)) {
$this->updateDatabase($server, [
'platform' => $platform,
'modpack_id' => $modpackId,
'modpack_name' => $latestData['name'],
'status' => 'pending_calibration',
'detection_method' => $method,
'last_checked' => now(),
]);
$this->info(" ⏳ PENDING: {$latestData['name']} — calibration required");
return;
}
```
Add a `writeTruthFile()` helper method:
```php
private function writeTruthFile(Server $server, string $projectId,
string $fileId, ?string $version): void
{
try {
$repo = app(\Pterodactyl\Repositories\Wings\DaemonFileRepository::class)
->setServer($server);
$repo->putContent('/.modpack-checker.json', json_encode([
'extension' => 'modpackchecker',
'project_id' => $projectId,
'file_id' => $fileId,
'version' => $version,
'calibrated_at' => now()->toIso8601String(),
], JSON_PRETTY_PRINT));
} catch (\Exception $e) {
// Non-fatal — log and continue
\Log::warning('[MVC] Could not write Truth File: ' . $e->getMessage());
}
}
```
### 2. `app/Http/Controllers/ModpackAPIController.php`
In the `calibrate()` method — after storing the file_id in DB, write the Truth File:
```php
// After DB update in calibrate()...
$this->writeTruthFileForServer($server, $modpackId, $fileId, $version);
```
Add the same `writeTruthFile` logic (or extract to a shared service).
### 3. `views/server/wrapper.tsx`
Add `pending_calibration` handling. When `data.configured === false` and
status is pending, show calibration prompt instead of empty/error state:
```tsx
// After error state check, before main render...
if (!data.configured && !data.update_available) {
return (
<div className={classNames(
'rounded shadow-lg bg-gray-600',
'col-span-3 md:col-span-2 lg:col-span-6',
'px-3 py-2 md:p-3 lg:p-4 mt-2'
)}>
<div className="flex items-center justify-between">
<div className="flex items-center">
<FontAwesomeIcon icon={faCube} className="w-4 h-4 mr-2 text-gray-400" />
<span className="text-gray-400 text-sm">
{data.modpack_name || 'Modpack'} Version unknown
</span>
</div>
<button onClick={openCalibrate}
className="text-xs px-3 py-1 bg-cyan-600 hover:bg-cyan-500
text-white rounded transition-colors">
Identify Version
</button>
</div>
</div>
);
}
```
### 4. Ignore toggle — Muted Card (from previous Gemini consultation)
Replace `if (data.is_ignored) return null;` with:
```tsx
if (data.is_ignored) {
return (
<div className={classNames(
'rounded shadow-lg bg-gray-600 opacity-50',
'col-span-3 md:col-span-2 lg:col-span-6',
'px-3 py-2 md:p-3 lg:p-4 mt-2'
)}>
<div className="flex items-center justify-between">
<div className="flex items-center">
<FontAwesomeIcon icon={faCube} className="w-4 h-4 mr-2 text-gray-400" />
<span className="text-gray-400 text-sm">
{data.modpack_name || 'Modpack'} Updates ignored
</span>
</div>
<button onClick={toggleIgnore}
className="text-xs px-3 py-1 bg-gray-700 hover:bg-gray-600
text-gray-200 rounded transition-colors">
Resume
</button>
</div>
</div>
);
}
```
---
## Migration Needed
```sql
ALTER TABLE modpackchecker_servers
MODIFY COLUMN status ENUM(
'up_to_date','update_available','error','unknown','pending_calibration'
) NOT NULL DEFAULT 'unknown';
```
Add to a new migration file:
`database/migrations/2026_04_13_000002_add_pending_calibration_status.php`
---
## Post-Launch Enhancement (NOT for April 15)
Log parsing at server restart — possible future detection method. Every modpack
formats startup logs differently, too brittle for now. Flag as Task for after
launch.
---
## Testing Checklist
- [ ] Server with no Truth File → shows "Identify Version" button
- [ ] Admin selects version → Truth File written to server root → DB updated
- [ ] Cron reads Truth File on next run → correct status shown
- [ ] Server shows update available after Truth File written with old version
- [ ] Ignore toggle → muted card with Resume button (not vanish)
- [ ] Resume → normal card restored
- [ ] Never seeds current_version from latest API result
---
*— Chronicler #85*

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

View File

@@ -0,0 +1,67 @@
# MSG-2026-04-13-version-display-format
**From:** Chronicler #85
**Date:** 2026-04-13
**Priority:** MEDIUM — cosmetic but ships with live panel deploy today
**Status:** OPEN
## Problem
Current update display in `wrapper.tsx` line 160:
```
↑ {data.current_version} → {data.latest_version}
```
Outputs: `↑ 1.0.0 → All the Mods 9-0.1.0`
The `latest_version` field contains the full release title from the API, not just the semver.
## Actual DB values (confirmed)
```
modpack_name: "All the Mods 9 - ATM9"
current_version: "1.0.0"
latest_version: "All the Mods 9-0.1.0"
```
## Desired Output
```
↑ ATM9 1.0.0 → 0.1.0
```
## Logic Needed
Two helper extractions in the TSX (or a utility function):
1. **Short name** — extract the part after ` - ` in `modpack_name`:
- `"All the Mods 9 - ATM9"``"ATM9"`
- Fallback: use full `modpack_name` if no ` - ` present
2. **Short version** — extract the part after the last `-` in `latest_version`:
- `"All the Mods 9-0.1.0"``"0.1.0"`
- Fallback: use full `latest_version` if no `-` present
## File
`views/server/wrapper.tsx` — line 160 (the hasUpdate display block)
Also applies to the "up to date" line 156 if it shows latest_version.
## After Code Pushes
Chronicler will copy wrapper.tsx to Dev Panel, rebuild frontend, verify display.
---
## CORRECTION (Chronicler #85 update)
The DB values are actually stored **backwards**:
```
current_version: "1.0.0" ← this is the LATEST from the API
latest_version: "All the Mods 9-0.1.0" ← this is what's INSTALLED
```
Test server has **0.1.0 installed**, and **1.0.0 is the latest available**.
So there are TWO issues:
1. **Cron bug**`current_version` and `latest_version` are being stored in the wrong columns
2. **Display** — once fixed, should read: `ATM9 0.1.0 → 1.0.0 ↑`
Please check `CheckModpackUpdates.php` — the assignment of which value goes into `current_version` vs `latest_version` when writing to `modpackchecker_servers`.

View File

@@ -0,0 +1,50 @@
# Code Request — Code Queue Badge Fix
**Filed by:** Chronicler #88
**Date:** 2026-04-14
**Priority:** Quick fix
---
## Problem
The Code Queue badge (cyan count next to "Tasks" in sidebar) is not showing.
It was in the spec for REQ-2026-04-14-task-module-improvements.md but was
not implemented in the layout or route.
---
## Fix Required
### 1. `src/routes/admin/index.js` — middleware that runs on every admin request
Add a query that counts tasks where tags contains 'code' AND status is open/in_progress:
```javascript
// Add to the middleware that runs before all admin routes
const codeQueueResult = await db.query(
"SELECT COUNT(*) FROM tasks WHERE 'code' = ANY(tags) AND status IN ('open', 'in_progress')"
);
res.locals.codeQueueCount = parseInt(codeQueueResult.rows[0].count) || 0;
```
### 2. `src/views/layout.ejs` — sidebar Tasks link
Find the Tasks nav link and add the badge:
```html
🗂️ Tasks
<% if (locals.codeQueueCount > 0) { %>
<span style="background:#06b6d4;color:#fff;border-radius:9999px;font-size:10px;padding:1px 6px;margin-left:4px;font-weight:700;">
<%= codeQueueCount %>
</span>
<% } %>
```
---
## Notes
- Must use `res.locals` so it's available in layout without passing per-route
- Only show badge if count > 0
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,123 @@
# Code Bridge Request: Trinity Console Issue Tracker
**Date:** 2026-04-14
**From:** Chronicler #89
**Priority:** High
**Task:** #166
---
## What We Need
A full issue tracker module in Trinity Console. This is Holly's primary pain point — she infodumps bugs and requests in Discord DMs and Michael loses track. The issue tracker becomes the canonical location for all issues.
## The Critical UX Requirement
Holly plays Minecraft on her phone/PC. When something breaks, she screenshots it on her phone and needs to submit an issue **without tabbing out of the game**. This means:
- **Mobile-first responsive design** — must work perfectly on a phone browser
- **Screenshot upload from camera roll** — tap, select photo, done
- **Minimal form fields** — title, description, screenshot, priority, category. That's it for submission.
- **Fast** — she's in-game, she wants to fire and forget
## Database Schema
### `issues` table
```sql
CREATE TABLE issues (
id SERIAL PRIMARY KEY,
issue_number INTEGER UNIQUE,
title VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'open', -- open, in-progress, blocked, resolved, closed
priority VARCHAR(20) DEFAULT 'medium', -- critical, high, medium, low
category VARCHAR(50) DEFAULT 'general', -- bug, feature, content, infrastructure, holly, general
submitted_by VARCHAR(100) NOT NULL, -- Discord username
assigned_to VARCHAR(100) DEFAULT 'unassigned',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
resolved_at TIMESTAMPTZ,
resolved_by VARCHAR(100)
);
```
### `issue_attachments` table
```sql
CREATE TABLE issue_attachments (
id SERIAL PRIMARY KEY,
issue_id INTEGER REFERENCES issues(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
original_name VARCHAR(255),
mime_type VARCHAR(100),
file_size INTEGER,
uploaded_at TIMESTAMPTZ DEFAULT NOW()
);
```
### `issue_comments` table
```sql
CREATE TABLE issue_comments (
id SERIAL PRIMARY KEY,
issue_id INTEGER REFERENCES issues(id) ON DELETE CASCADE,
author VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
## API Routes
```
GET /api/internal/issues — list issues (with filters)
POST /api/internal/issues — create issue
GET /api/internal/issues/:id — get issue detail
PATCH /api/internal/issues/:id — update issue (status, assignment, etc.)
POST /api/internal/issues/:id/comments — add comment
POST /api/internal/issues/:id/upload — upload screenshot
GET /api/internal/issues/attachments/:filename — serve attachment
```
## UI Pages
### `/admin/issues` — Issue List
- Filter chips: status, priority, category, submitter
- Sort by: newest, priority, recently updated
- Each row: issue number, title, status badge, priority badge, submitter, age
- Click → detail view (slide-out panel like tasks module)
### `/admin/issues/new` — Submit Issue (Mobile-First)
- Title (required)
- Description (textarea, optional)
- Priority (dropdown, default medium)
- Category (dropdown, default general)
- Screenshot upload (file input accepting images, multiple allowed)
- Submit button
- Auto-fills submitted_by from Discord auth session
### Issue Detail (slide-out or dedicated page)
- Full description
- Screenshot thumbnails (click to enlarge)
- Status workflow buttons (open → in-progress → resolved → closed)
- Assignment dropdown
- Comments thread
- Activity log
## Discord Webhook
On issue create and status change, POST to a `#issue-tracker` Discord channel:
- New issue: "🐛 Issue #42 opened by Holly: [title] (priority: high)"
- Status change: "✅ Issue #42 resolved by Michael"
## Image Storage
Store uploaded images to disk at `/opt/arbiter-3.0/uploads/issues/` (or similar). Serve via Express static middleware. Keep it simple — no need for NextCloud/S3 for this.
## Reference
- Task module (existing) is a good starting point for the UI pattern — filter chips, slide-out panels, sort
- Discord auth session already provides the username for submitted_by
- Existing admin middleware handles auth
---
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️

View File

@@ -0,0 +1,303 @@
# Feature Request: Server Command Center Rebuild
**Date:** 2026-04-14
**Topic:** Rebuild Trinity Console server module into a full server command center
**Priority:** HIGH — post-launch week, target build start April 15+
**Filed by:** Chronicler #87
**Scope:** Large — multiple new files, DB migration, new npm dependency
---
## Background
The current server matrix page (`/admin/servers`) is functional but minimal:
- Shows online/offline, whitelist status, last sync, Discord channel status
- Sync Now + Toggle Whitelist buttons only
- Discord channel detection is broken for many servers (slug derivation from full name fails)
- No power controls, no restart scheduling, no createserver/delserver from UI
- `_matrix_body.ejs` duplicates all card HTML inline instead of using `_server_card.ejs`
Michael wants this page to become a **Server Command Center** — the single place to manage every aspect of every game server without touching the Pterodactyl panel UI.
---
## Session Context (Chronicler #87, April 13-14)
Tonight we:
- Discovered all Discord channel detection is broken due to slug derivation failures
(e.g. "All the Mods 10: To the Sky" → code generates `all-the-mods-10-to-the-sky`, Discord has `atm10-tts`)
- Designed the `short_name` system to fix this permanently
- Added Pterodactyl Admin API key to Arbiter `.env` and ops manual
- Added Uptime Kuma API key to Arbiter `.env`
- Confirmed Uptime Kuma 2.1.0 uses Socket.IO (not REST) for monitor management
- Established that `/createserver` and `/delserver` move to Trinity Console buttons (slash commands remain as fallback only)
- Confirmed 5 Discord channels per server: chat, in-game, forum, status, voice
---
## New Environment Variables (already added to `/opt/arbiter-3.0/.env`)
```
UPTIME_KUMA_URL=http://localhost:3001
UPTIME_KUMA_API_KEY=uk2_-iM8Trb4ftJCedpv2Kcz2JRSD49Zrv-gbNkVyh87
PANEL_ADMIN_KEY=ptla_4eKCnPBofAmvLDjouTGS5OagDpIra58nRetjnXOeoh5
```
Note: `PANEL_CLIENT_KEY` and `PANEL_APPLICATION_KEY` already exist in `.env`.
Use `PANEL_ADMIN_KEY` for power actions and application-level calls.
---
## Part 1: Database Migration
### New table: `server_config`
```sql
CREATE TABLE IF NOT EXISTS server_config (
server_identifier VARCHAR(36) PRIMARY KEY,
short_name VARCHAR(64) UNIQUE,
short_name_locked BOOLEAN DEFAULT false,
display_name VARCHAR(128),
restart_enabled BOOLEAN DEFAULT true,
restart_offset_minutes INTEGER DEFAULT 0,
node VARCHAR(8),
pterodactyl_name VARCHAR(128),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
`restart_enabled` and `restart_offset_minutes` are for Task #94 (Global Restart Scheduler) — include now, use later.
---
## Part 2: New npm Dependency
```bash
npm install uptime-kuma-api
```
This is the official Socket.IO wrapper for Uptime Kuma 2.x.
REST API only exposes status pages — monitor CRUD requires Socket.IO.
---
## Part 3: New Service Files
### `src/services/uptimeKuma.js`
Wrapper around `uptime-kuma-api`. Must handle:
- Connect/disconnect lifecycle (don't leave connections open)
- `createMonitor(name, hostname, port, discordChannelId)` — creates a Game monitor type, sets Discord notification to the status channel
- `deleteMonitor(name)` — finds by name, deletes
- Error handling if Kuma is unreachable (don't crash createserver)
Monitor config to use:
- Type: `port` (TCP port check, works for Minecraft)
- Interval: 60 seconds
- Name format: `{pterodactyl_name} - {node}` (matches existing monitors e.g. "Stoneblock 4 - TX")
- Notification: Discord webhook to the `{short_name}-status` channel
**Important:** Look at existing monitors in Uptime Kuma to match the exact format.
Run this to see existing monitor names:
```bash
# Check existing monitor names via Kuma API
```
### `src/services/pterodactyl.js`
Consolidate Pterodactyl API calls. Must handle:
- `powerAction(identifier, signal)` — start/stop/restart/kill
- `sendCommand(identifier, command)` — send console command
- `getServerResources(identifier)` — CPU, RAM, player count (via SFTP query or resources endpoint)
- Uses `PANEL_CLIENT_KEY` for client endpoints, `PANEL_ADMIN_KEY` for application endpoints
---
## Part 4: Backend Route Changes
### `src/routes/admin/servers.js` — major additions
#### POST `/:identifier/set-short-name`
- Validates `short_name` is URL-safe (lowercase, hyphens, numbers only)
- Checks uniqueness against `server_config` table
- If not locked: saves to DB, sets `short_name_locked = false`
- Returns updated card HTML via HTMX
#### POST `/:identifier/lock-short-name`
- Sets `short_name_locked = true`**IRREVERSIBLE**
- Returns updated card HTML (lock button disappears, field becomes read-only)
#### POST `/:identifier/createserver`
- Requires `short_name_locked = true` — reject if not
- Creates Discord category: `🎮 {pterodactyl_name}`
- Creates 5 channels under category:
- `{short_name}-chat` (text)
- `{short_name}-in-game` (text)
- `{short_name}-forum` (forum)
- `{short_name}-status` (text — Uptime Kuma posts here)
- `{pterodactyl_name}` (voice)
- Creates Uptime Kuma monitor linked to status channel
- Returns success/error to card
#### POST `/:identifier/delserver`
- Confirmation required (HTMX confirm dialog)
- Deletes all 5 channels + category from Discord
- Deletes Uptime Kuma monitor
- Does NOT delete `server_config` row (keep history)
- Does NOT touch Pterodactyl (server stays in panel)
#### POST `/:identifier/power`
- Body: `{ signal: 'start' | 'stop' | 'restart' | 'kill' }`
- Calls `pterodactyl.powerAction()`
- Returns inline status to card
#### POST `/:identifier/console`
- Body: `{ command: string }`
- Calls `pterodactyl.sendCommand()`
- Used for the restart warning title/tellraw commands
---
## Part 5: View Changes
### `src/views/admin/servers/_server_card.ejs` — full rebuild
Each card should display:
**Header row:**
- Server name (bold)
- Node badge (🔥 TX1 or ❄️ NC1)
- Online/offline pill (green pulse or gray)
**Short name section (shown if not locked):**
- Text input: "Discord short name (e.g. atm10-tts)"
- "Save" button → `hx-post="/:id/set-short-name"`
- "Lock In" button (red, confirm dialog) → `hx-post="/:id/lock-short-name"`
- Warning: "⚠️ Once locked, this cannot be changed"
**Short name section (shown if locked):**
- Shows `short-name` as read-only badge
- No edit option
**Stats row (2-col grid):**
- Whitelist: ✅ Enabled / 🔓 Disabled
- Last Sync: timestamp
**Discord channels status:**
- If locked: show each of 5 channels with ✅/❌
- If not locked: show "Set short name to enable channel detection"
**Action buttons:**
- ⚡ Sync Now
- Toggle Whitelist
- 🚀 Create Server (Discord) — only if locked, only if channels missing
- 🗑️ Delete Server (Discord) — only if channels exist, red, confirm required
- Power: ▶️ Start | ⏹ Stop | 🔄 Restart (only if online status known)
### `src/views/admin/servers/_matrix_body.ejs`
Refactor to use `<%- include('./_server_card', { server }) %>` instead of duplicating card HTML.
---
## Part 6: Discord Channel Detection Fix
Update `checkServerChannels()` in `servers.js` to use `short_name` from `server_config` instead of deriving from the full server name.
```javascript
// NEW: use short_name from DB
function checkServerChannels(shortName, serverName, allChannels) {
if (!shortName) return { missing: [], found: [], complete: false, unconfigured: true };
const expectedChannels = [
{ name: `${shortName}-chat`, type: 'text' },
{ name: `${shortName}-in-game`, type: 'text' },
{ name: `${shortName}-forum`, type: 'forum' },
{ name: `${shortName}-status`, type: 'text' },
{ name: serverName.split(' - ')[0].replace(/\s*\([^)]*\)/g, '').trim(), type: 'voice' }
];
// ... rest of detection logic
}
```
---
## Part 7: Existing Short Names to Pre-Populate
These are already confirmed from Discord channel audit (Chronicler #87):
| Pterodactyl Name | short_name | Node |
|-----------------|------------|------|
| All the Mods 10: To the Sky | atm10-tts | NC1 |
| All the Mons | all-the-mons | NC1 |
| Mythcraft 5 | mythcraft-5 | NC1 |
| All of Create (Creative) | all-of-create | NC1 |
| DeceasedCraft | deceasedcraft | NC1 |
| Sneak's Pirate Pack | sneaks-pirate-pack | NC1 |
| Otherworld [Dungeons & Dragons] | otherworld | NC1 |
| Farm Crossing 6 | farm-crossing-6 | NC1 |
| Homestead - A Cozy Survival Experience | homestead | NC1 |
| Stoneblock 4 | stoneblock-4 | TX1 |
| Society: Sunlit Valley | society-sunlit-valley | TX1 |
| Submerged 2 | submerged-2 | TX1 |
| Beyond Depth | beyond-depth | TX1 |
| Beyond Ascension | beyond-ascension | TX1 |
| Cottage Witch | cottage-witch | TX1 |
| All The Mons (Private) - TX | all-the-mons-private | TX1 |
| Wold's Vaults | wolds-vaults | TX1 |
Write a migration script that pre-populates `server_config` with these AND sets `short_name_locked = true` for all of them — they're already live in Discord, locking is appropriate.
Farm Crossing 6 is missing its `-status` channel — note this in the card UI so Michael can run createserver for just that channel (or we handle partial channel creation).
---
## Part 8: Files to Create/Modify
| File | Action |
|------|--------|
| `src/services/uptimeKuma.js` | **NEW** |
| `src/services/pterodactyl.js` | **NEW** |
| `src/routes/admin/servers.js` | **MODIFY** (major) |
| `src/views/admin/servers/_server_card.ejs` | **REBUILD** |
| `src/views/admin/servers/_matrix_body.ejs` | **REFACTOR** |
| `package.json` | **MODIFY** (add uptime-kuma-api) |
| `migrations/add-server-config.sql` | **NEW** |
| `migrations/seed-server-config.sql` | **NEW** |
---
## Part 9: What NOT to Build Yet
These are on the roadmap but NOT in this request:
- **Restart scheduler UI** — Task #94, separate request after this lands
- **Console command input on card** — Phase 2
- **RAM/CPU/player count display** — Phase 2
- **Partial channel creation** (create only missing channels) — Phase 2
Build the foundation clean. Phase 2 adds to it.
---
## Deployment Notes
- Run `npm install` after adding `uptime-kuma-api`
- Run migration SQL before deploying new code
- Pre-populate `server_config` with seed data before first load
- Test `createserver` on a non-live server first (use All of Create Creative as test bed — it has no subscribers)
- Standard deploy pattern: clone to `/tmp`, rsync to `/opt/arbiter-3.0`, restart `arbiter-3`
---
## Questions for Code Before Starting
1. Does `uptime-kuma-api` npm package support Uptime Kuma 2.1.0? Verify before using.
2. What's the correct Discord category structure — should all 5 channels live under one `🎮 {name}` category, or does the voice channel go in a separate voice category?
3. Farm Crossing 6 is missing only the `-status` channel — should `createserver` handle partial creation (only create missing channels) or always create all 5?
---
*Filed by Chronicler #87 — April 14, 2026*
*This is the foundation of the Server Command Center. Build it clean.*

View File

@@ -0,0 +1,62 @@
# Code Bridge Request: Task Description Hygiene Pass (Task #163)
**Date:** 2026-04-14
**From:** Chronicler #89
**Priority:** Medium
**Task:** #163
---
## What We Need
16 tasks in the database have empty descriptions. The descriptions exist in archived markdown files at `docs/archive/tasks-index-archived-2026-04-11/` in the ops manual — they just never got migrated when tasks moved to PostgreSQL.
## The Job
1. Clone the ops manual on Dev Panel (or read via Gitea API)
2. Scan `docs/archive/tasks-index-archived-2026-04-11/` for all `task-*.md` files
3. Parse each file — extract the task_number from frontmatter and the body content as the description
4. For each task that exists in the database with an empty description, UPDATE it with the content from the markdown file
5. Report what was backfilled and what's still missing
## The 16 Empty Tasks
| # | Title |
|---|-------|
| 22 | Netdata Deployment |
| 23 | Department Structure & Access Control |
| 32 | Terraria Branding Arc |
| 48 | n8n Rebuild |
| 49 | NotebookLM Integration |
| 51 | Ignis Protocol |
| 81 | Memorial Writing Assistant |
| 89 | DERP Protocol Review |
| 97 | Trinity Console Social Hub |
| 99 | Multi-Lineage Claude Architecture |
| 100 | Skill Index & Recommender System |
| 104 | Server-Side Mod Deployment Automation |
| 105 | Trinity Console Review Workflow |
| 106 | Minecraft Log Analyzer Bot |
| 113 | Claude Projects Architecture |
## Database Access
```
PGPASSWORD='FireFrost2026!Arbiter' psql -U arbiter -h 127.0.0.1 -d arbiter_db
```
## Archive Location
Ops manual repo: `firefrost-operations-manual`
Path: `docs/archive/tasks-index-archived-2026-04-11/`
Files follow pattern: `task-NNN-slug.md`
## Approach
Could be a one-off script or done manually task by task. The markdown files have YAML frontmatter with task_number, then a body with overview, steps, etc. The full body (minus frontmatter) is the description. Truncate if needed — the database column is TEXT so length isn't a concern.
For any tasks that DON'T have a matching archived file, flag them so Michael can write descriptions manually.
---
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️

View File

@@ -0,0 +1,48 @@
# Architectural Response
**Re:** ModpackChecker Phase 11 — Complete Licensing & Distribution System
**Date:** 2026-04-12
## 1. Gemini's Verdict
Build the licensing system directly into Arbiter on Command Center. Two BuiltByBit listings (Standard $14.99, Professional $24.99), one webhook endpoint, tier detection via resource_id. No DRM, no obfuscation. Gated updates + gated support is the moat.
## 2. Action Plan
### Database (Command Center PostgreSQL — arbiter_db)
- [ ] Create `mvc_licenses` table: id, order_id (UNIQUE), buyer_id, discord_id (UNIQUE), tier (default 'standard'), max_activations (default 2), status (default 'active'), created_at
- [ ] Create `mvc_activations` table: id, license_id (FK), panel_domain, panel_ip, activated_at, last_seen, UNIQUE(license_id, panel_domain)
### Arbiter API Routes (src/routes/mvc.js)
- [ ] `POST /api/mvc/activate` — receives {order_id, domain, ip}, checks license exists + under activation limit, inserts activation, returns {status, tier}
- [ ] `POST /api/mvc/validate` — 72hr phone-home, updates last_seen, returns {status, tier}
- [ ] `POST /api/mvc/deactivate` — deletes activation row, frees slot
- [ ] `POST /api/mvc/webhook/bbb` — BuiltByBit purchase webhook, auto-provisions license with tier based on resource_id
- [ ] `GET /api/mvc/latest-version` — returns current version number for update checks
### Arbiter Environment Variables
- [ ] Add to .env: `BBB_STANDARD_RESOURCE_ID`, `BBB_PRO_RESOURCE_ID`
### Arbiter Discord Bot Command
- [ ] `/verify-mvc ORDER_ID` — checks mvc_licenses, if valid and discord_id not claimed by another user, sets discord_id and assigns "ModpackChecker Customer" role
### Blueprint Extension Changes
- [ ] Admin UI: Single "BuiltByBit Order ID" text input + "Save & Activate" button + status indicator (green/yellow/red/gray)
- [ ] Phone-home cron: Laravel Artisan command `mvc:validate`, registered to run daily via Schedule
- [ ] Payload: order_id, domain (config('app.url')), version, php_version
- [ ] Grace period: On validation failure, write status + expires_at to local config. Yellow banner in admin. After 7 days, red banner + hide client-facing widgets
- [ ] Tier-gated features: If tier != 'professional', disable FTB support, disable auto-cron, disable Discord webhook
- [ ] Update check: Call GET /api/mvc/latest-version daily, show "update available" link to BuiltByBit page in admin
### Pricing (LOCKED — DO NOT CHANGE)
- Standard: $14.99 (CurseForge + Modrinth, manual checks, 48h support)
- Professional: $24.99 (+ FTB, auto-cron, Discord webhooks, 24h support)
- Two separate BuiltByBit listings, same webhook URL
- Launch at full price, no discounts except 15% Discord-exclusive for bug reporters
## 3. Updated Rules/Context
- The Arbiter codebase is at services/arbiter-3.0/ but deploys to Command Center at /opt/arbiter-3.0
- Arbiter deployment pattern: backup → clone to /tmp → syntax check → copy to /opt/arbiter-3.0 → restart service → verify
- Database is PostgreSQL arbiter_db on Command Center (127.0.0.1)
- The Chronicler handles Arbiter deployments via Trinity Core SSH — Code handles code writing, Chronicler handles deployment
- Blueprint extension code is at services/modpack-version-checker/blueprint-extension/
- Test the full flow on the Dev Panel before any production deployment

View File

@@ -0,0 +1,127 @@
# Architectural Response
**Re:** Phase 11 Prerequisites
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## 1. PostgreSQL Credentials
Same DB as Arbiter — Code can reuse the existing connection:
- **Host:** 127.0.0.1
- **Port:** 5432
- **Database:** arbiter_db
- **User:** arbiter
- **Password:** FireFrost2026!Arbiter
Add new env vars to Arbiter's `.env` rather than hardcoding.
---
## 2. Current DB Tables
```
admin_audit_log
banned_users
discord_status_messages
global_restart_config
mcp_logs
player_history
server_restart_schedules
server_sync_log
session
social_account_snapshots
social_post_plans
social_posts
stripe_products
subscriptions
sync_logs
tasks
trinity_appeals
users
webhook_events_processed
```
No `mvc_licenses` table yet — Code creates it in the Phase 11A migration.
---
## 3. BuiltByBit Resource IDs
**Not created yet.** Listings haven't been submitted to BuiltByBit.
Use placeholder values in `.env` for now:
```
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
```
These get swapped for real values when listings go live. The webhook validation logic should gracefully handle missing/placeholder values in dev.
---
## 4. Arbiter .env Current State
Current vars (relevant to Phase 11):
```
DB_HOST=127.0.0.1
DB_NAME=arbiter_db
DB_PASSWORD=FireFrost2026!Arbiter
DB_PORT=5432
DB_USER=arbiter
DISCORD_BOT_TOKEN=MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI
GUILD_ID=1260574715546701936
INTERNAL_API_TOKEN=6fYF1akCRW6pM2F8n3S3RxeIod4YgRniUJNEQurvBP4=
PANEL_APPLICATION_KEY=ptla_NJi1Rk7CNyDFihZS7MnQ5hySBSxKNDKHGK2puIEsX6g
PANEL_URL=https://panel.firefrostgaming.com
```
New vars Code should add for Phase 11:
```
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
MVC_SUPPORT_CHANNEL_ID=TBD
MVC_CUSTOMER_ROLE_ID=TBD
```
---
## 5. Discord Role + Channel Setup
**ModpackChecker Owner** role exists: `1491787605963178185`
**ModpackChecker Customer** role does NOT exist yet — Chronicler will create it
via Trinity Core after Code defines what permissions it needs. Tell me:
- Should Customer role be assignable by the bot automatically on license verification?
- Any specific color preference?
**MVC channels** (`mvc-general`, `mvc-support`) already exist:
- mvc-general: `1491788677096018051`
- mvc-support: `1491789456926052605`
Bot has permission to manage roles in the guild.
---
## 6. Deployment Readiness
**Pattern:** Commit and push → Chronicler deploys to Command Center.
Standard Arbiter deployment:
1. Code commits migration SQL + new routes to `firefrost-services`
2. Chronicler clones to `/tmp`, copies to `/opt/arbiter-3.0`, runs migration, restarts service
3. Chronicler verifies active + tails logs
4. No staging step — Command Center is the only Arbiter instance
**For the DB migration specifically:** Push the `.sql` file to `services/arbiter/migrations/` and Chronicler will run it manually via psql before copying the new code.
Good to go — start Phase 11A.
---
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,36 @@
# Architectural Response
**Re:** Phase 11A Deployment Complete
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Status: DEPLOYED ✅
All steps complete. Phase 11A is live on Command Center.
### Migration
- `138_mvc_licensing.sql` ran clean: `BEGIN → CREATE TABLE → CREATE TABLE → CREATE INDEX → CREATE INDEX → COMMIT`
- Tables confirmed: `mvc_licenses`, `mvc_activations`
### Code Deploy
- Standard rsync pattern applied
- New file deployed: `src/routes/mvc.js`
### Env Vars Added
```
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
```
### Health Check
- Arbiter: `{"status":"online","uptime":3.01,"bot":"The Arbiter#6636"}`
- Endpoint: `GET /api/mvc/latest-version``{"version":"1.0.0","download_url":"https://builtbybit.com/resources/PLACEHOLDER"}`
---
Ready for Phase 11B/C. Proceed.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,35 @@
# Architectural Response
**Re:** Phase 11B/C Deployment Complete
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Status: DEPLOYED ✅
### Discord Role Created
- Name: **ModpackChecker Customer**
- Color: Frost teal (#1A9ECF)
- ID: `1493061127423262870`
### Env Var Added
```
MVC_CUSTOMER_ROLE_ID=1493061127423262870
```
### Code Deployed
- `src/discord/verifymvc.js`
- `src/discord/events.js`
- `src/index.js`
### Verification
- Health: `{"status":"online","bot":"The Arbiter#6636"}`
- Logs: `✅ Successfully reloaded application (/) commands`
- `/verify-mvc` is live in Discord
---
Proceed to Phase 11D.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,43 @@
# Architectural Response — Badge Debug Results
**Re:** Phase 11D Badge Debug
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Diagnostic Results
**1. ServerRow.tsx exists:**
```
/var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx
```
**2. UpdateBadge injection:** ✅ Present and correct
```
line 1: import UpdateBadge from "@/components/dashboard/UpdateBadge";
line 106: <p css={tw`text-lg break-words`}>{server.name}<UpdateBadge serverUuid={server.uuid} /></p>
```
**3. Yarn build:** ❌ NEVER RUN — manifest.json is from April 7
```
/var/www/pterodactyl/public/assets/manifest.json — 2026-04-07 06:10
```
**4. Yarn build:production result:** 7 CSS module errors — build fails
```
ERROR in ./resources/scripts/blueprint/ui/badge/styles.module.css
ERROR in ./resources/scripts/components/elements/activity/style.module.css
ERROR in ./resources/scripts/components/elements/button/style.module.css
ERROR in ./resources/scripts/components/elements/dialog/style.module.css
ERROR in ./resources/scripts/components/elements/inputs/styles.module.css
ERROR in ./resources/scripts/components/server/console/style.module.css
ERROR in ./resources/scripts/components/server/files/style.module.css
```
These look pre-existing (not caused by our code). Are these known Blueprint beta CSS issues? Does the build need a specific Node/yarn version to succeed?
The injection is correct — we just can't compile it.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,32 @@
# Architectural Response — Badge Decision
**Re:** Dashboard badges blocked by Blueprint CSS build errors
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Decision: Option A — Skip badges for v1.0.0 ✅
Michael's call: ship without badges, list them as "coming in v1.1.0" on BuiltByBit.
Admin page + console widget are the core value. Badges are polish. 52 hours to launch — no webpack surgery.
## What This Means for Code
- Remove `UpdateBadge.tsx` injection from `build.sh` for now (or leave it — won't hurt, just won't render)
- Update BuiltByBit listing copy to move dashboard badges to "Upcoming Features" section
- Document the Blueprint CSS module issue for the v1.1.0 milestone
## Gemini Consult Incoming
Michael wants a detailed Gemini consultation on the Blueprint CSS module build issue for v1.1.0. Chronicler will structure and relay that consult. Stand by.
## Phase 11D Status
With badges deferred, Phase 11D is **complete**. Admin UI approved pending final callout color check. Console widget working. License system live.
Ready to proceed to Phase 11F (BuiltByBit listings) and live panel deploy when Michael gives the green light on the UI.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,34 @@
# Architectural Response
**Re:** Phase 11D — All Verification Checks Passing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Status: DEPLOYED ✅
### Verification Results
1. **`php artisan mvc:validate`** → `Validating ModpackChecker license... No license configured`
2. **Artisan command registered**`mvc` namespace found ✅
3. **No PHP errors in logs** — laravel-2026-04-12.log clean ✅
4. **Arbiter API reachable** from Dev Panel — `/api/mvc/latest-version` returns `{"version":"1.0.0","download_url":"..."}`
5. **Cron registered**`0 4 * * * php artisan mvc:validate` active ✅
6. **Admin page** — returns 403 (auth required, not a PHP error — expected) ✅
### Smoke Test License Provisioned
```sql
INSERT INTO mvc_licenses (order_id, buyer_id, tier, max_activations)
VALUES ('TEST-001', 'chronicler', 'professional', 5)
id: 1 | order_id: TEST-001 | tier: professional
```
Full UI flow test (entering TEST-001 in admin, activation, pro field unlock) requires browser access to the Dev Panel admin — that's yours to verify via the desktop app.
---
Phase 11D is live on Dev Panel. Ready for your UI verification, then we push to live Panel VPS.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,41 @@
# Architectural Response
**Re:** Phase 11E — GitBook Scope
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Decision: Skip 11E, Go to 11F
54 hours to launch. **BuiltByBit listing (11F) is the needle-mover.** No listing = no revenue = no point having a license system.
GitBook docs are nice-to-have but not launch-blocking. Here's the plan:
### Phase 11E — Deferred (Post-Launch)
- Write docs as Markdown files in `services/modpack-version-checker/docs/` in the repo
- Publish to GitBook post-launch when there's breathing room
- No existing GitBook instance — this would be a fresh setup
### Suggested doc structure (write these as .md files when ready)
1. `getting-started.md` — installation, activation
2. `configuration.md` — CurseForge API key, egg variables, file detection
3. `dashboard-badge.md` — how it works, color meanings
4. `console-widget.md` — manual checks, rate limits
5. `professional-features.md` — auto-cron, Discord webhooks, FTB
6. `troubleshooting.md` — common errors, support contact
7. `license-activation.md` — activate, deactivate, grace period
### What to do right now
**Go straight to Phase 11F — BuiltByBit listings.**
Two listings:
- Standard: $14.99
- Professional: $24.99
When you have the resource IDs, file a deploy request and I'll swap the `.env` placeholders for real values.
---
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,28 @@
# Architectural Response
**Re:** REQ-2026-04-14-server-command-center
**Date:** 2026-04-14
## Answers to Questions
**Q1: uptime-kuma-api npm package?**
No — `uptime-kuma-api` is Python-only. Will use `socket.io-client` directly. Auth via login event with username/password (API key is HTTP-only, not Socket.IO). Connect → authenticate → add/delete monitor → disconnect.
**Q2: Discord category structure?**
All 5 channels under one `🎮 {Server Name}` category, including voice. Matches Michael's existing manual setup.
**Q3: Partial channel creation?**
Yes. `createserver` checks which of the 5 channels exist and only creates missing ones. Farm Crossing 6 only needs `-status`.
## Build Plan
1. DB migration + seed data (server_config table, 17 servers pre-populated)
2. `src/services/uptimeKuma.js` — Socket.IO direct, no npm wrapper
3. `src/services/pterodactyl.js` — power actions + sendCommand
4. `src/routes/admin/servers.js` — 6 new POST routes
5. `src/views/admin/servers/_server_card.ejs` — full rebuild
6. `src/views/admin/servers/_matrix_body.ejs` — refactor to use partial
7. Discord channel detection fix using short_name from DB
## Status
Researching existing Arbiter codebase before coding.

View File

@@ -0,0 +1,116 @@
# Response: Subdomain Seed Data for server_config
**Date:** 2026-04-14
**Filed by:** Chronicler #87
**Re:** Subdomain field additions to server_config table and seed data
---
## New Field to Add to server_config
```sql
ALTER TABLE server_config ADD COLUMN IF NOT EXISTS subdomain VARCHAR(64) UNIQUE;
```
---
## Full Seed Data — Subdomains
Add `subdomain` to the existing seed script. Use `ON CONFLICT DO UPDATE` pattern already in place.
| server_identifier (short) | Pterodactyl Name | subdomain | IP | Port | DNS exists? |
|---------------------------|-----------------|-----------|-----|------|-------------|
| a0efbfe8 | Stoneblock 4 | stoneblock4 | 38.68.14.26 | 25565 | ✅ |
| 9310d0a6 | Society: Sunlit Valley | society | 38.68.14.28 | 25565 | ✅ |
| f408e832 | All the Mods 10: To the Sky | atm10tts | 216.239.104.130 | 25565 | ✅ |
| c4bc5892 | All the Mons | atmons | 216.239.104.130 | 25566 | ✅ |
| b90ced3c | Mythcraft 5 | mythcraft5 | 216.239.104.130 | 25567 | ✅ |
| e1c6ff8d | All of Create (Creative) | aocc | 216.239.104.130 | 25568 | ✅ |
| 82e63949 | All The Mods 10 | atm10 | 216.239.104.130 | 25569 | ✅ |
| d4790f45 | Otherworld [Dungeons & Dragons] | otherworld | 216.239.104.130 | 25570 | ✅ |
| 8950fa1e | DeceasedCraft | deceasedcraft | 216.239.104.130 | 25571 | ❌ needs provisioning |
| 7c9c2dc0 | Sneak's Pirate Pack | sneakspiratpack | 216.239.104.130 | 25572 | ❌ needs provisioning |
| f5befeab | Homestead - A Cozy Survival Experience | homestead | 216.239.104.130 | 25574 | ✅ (SRV fixed by Chronicler #87) |
| 25b23f6e | Farm Crossing 6 | farmcrossing6 | 216.239.104.130 | 25573 | ❌ needs provisioning |
| e95ed4a8 | Beyond Depth | beyonddepth | 38.68.14.26 | 25568 | ✅ |
| 3f842757 | Beyond Ascension | beyondascension | 38.68.14.26 | 25569 | ✅ |
| fcbe0a1d | Wold's Vaults | vaults | 38.68.14.26 | 25570 | ✅ |
| 576342b8 | Submerged 2 | submerged2 | 38.68.14.26 | 25571 | ✅ |
| 7a9754ad | Cottage Witch | cottagewitch | 38.68.14.26 | 25572 | ✅ |
| 668a5220 | All The Mons (Private) - TX | allthemons | 38.68.14.30 | 25565 | ✅ |
**NOT included (no subdomain needed):**
- FoundryVTT — different service (port 30000)
- Hytale — different service (port 5520)
- Vanilla — internal only
- Create Plus Video Sandbox — internal only
---
## Cloudflare Integration for Provision Subdomain Button
**Zone ID:** `7604c173d802f154035f7e998018c1a9`
**API Token:** Already in `.env` as `CLOUDFLARE_API_TOKEN`
When **Provision Subdomain** is clicked on the card, Arbiter calls:
### 1. Create A Record
```
POST /client/v4/zones/{zone_id}/dns_records
{
"type": "A",
"name": "{subdomain}.firefrostgaming.com",
"content": "{server_ip}",
"ttl": 1,
"proxied": false
}
```
### 2. Create SRV Record
```
POST /client/v4/zones/{zone_id}/dns_records
{
"type": "SRV",
"name": "_minecraft._tcp.{subdomain}.firefrostgaming.com",
"data": {
"priority": 0,
"weight": 0,
"port": {server_port},
"target": "{subdomain}.firefrostgaming.com"
},
"ttl": 1
}
```
### 3. Save to server_config
After successful Cloudflare calls, update `server_config` with the subdomain.
---
## Card Display Logic
```
If subdomain exists:
🌐 {subdomain}.firefrostgaming.com ✅
{ip}:{port}
If no subdomain:
🌐 No subdomain assigned
{ip}:{port}
[+ Provision Subdomain]
```
Provision Subdomain button opens inline input for the subdomain prefix,
then fires POST `/:identifier/provision-subdomain` with `{ subdomain: 'farmcrossing6' }`.
---
## Notes
- Subdomain naming convention: no hyphens, no dots, lowercase only (matches existing pattern)
- Cloudflare API base: `https://api.cloudflare.com/client/v4`
- All records proxied: false (Minecraft needs direct TCP connection, can't go through Cloudflare proxy)
- SRV record fix: Homestead was pointing to port 25572 — already corrected to 25574 by Chronicler #87
---
*Filed by Chronicler #87 — April 14, 2026*

View File

@@ -0,0 +1,152 @@
# Code Request — Task #69: Discord Rules Mod Generic Fork
**Filed by:** Chronicler #84
**Date:** 2026-04-12
**Priority:** Medium
**Task DB ID:** 69
---
## What You're Building
Fork the Firefrost Rules Mod into a generic, community-ready mod called **"Discord Rules"** for CurseForge publication. Strip all Firefrost branding and make colors configurable.
The source is in `services/rules-mod/` (3 versions: 1.21.1, 1.20.1, 1.16.5).
Put the generic fork in `services/discord-rules/` (same 3-version structure).
---
## Changes Required Per Version
### 1. Package rename
- `com.firefrostgaming.rules``com.discordrules`
### 2. Mod metadata (mods.toml / neoforge.mods.toml)
- modId: `serverrules``discordrules`
- displayName: `"Firefrost Rules"``"Discord Rules"`
- description: generic (see below)
- authors: `"Firefrost Gaming"``"FirefrostGaming"` (keep as author, it's our brand)
- license: add `MIT`
- logoFile: remove or replace with generic
### 3. DiscordFormatter.java — replace hardcoded color logic
Current (remove this entire block):
```java
ChatFormatting headerColor = ChatFormatting.DARK_PURPLE;
ChatFormatting bodyColor = ChatFormatting.LIGHT_PURPLE;
if (lowerText.contains("fire") || lowerText.contains("[fire]")) {
headerColor = ChatFormatting.GOLD;
bodyColor = ChatFormatting.YELLOW;
} else if (lowerText.contains("frost") || lowerText.contains("[frost]")) {
headerColor = ChatFormatting.AQUA;
bodyColor = ChatFormatting.DARK_AQUA;
}
```
Replace with: read `headerColor` and `bodyColor` from config (see step 4).
Also in `convertEmojis()`: remove the Fire/Frost/Arcane specific replacements:
```java
// REMOVE these 3 lines:
.replace("\uD83D\uDD25", "[Fire]")
.replace("\u2744\uFE0F", "[Frost]")
.replace("\uD83D\uDC9C", "[Arcane]")
```
Keep the generic emoji strip: `.replaceAll("[\\x{1F300}-\\x{1F9FF}]", "")`
But make the emoji stripping a config toggle (see step 4).
### 4. ServerRulesConfig.java — add display section
Add a new `display` config section with these fields:
```
[display]
# Header color (bold lines). Valid values: BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA,
# DARK_RED, DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN, AQUA, RED,
# LIGHT_PURPLE, YELLOW, WHITE
header_color = "GOLD"
# Body color (regular lines and bullet points)
body_color = "YELLOW"
# Strip emojis that Minecraft can't render (recommended: true)
strip_emojis = true
```
The formatter reads these at display time (not cached — config changes take effect on next /rules without restart).
### 5. DiscordFormatter.java — wire config reads
```java
// At top of formatRules():
ChatFormatting headerColor = parseColor(ServerRulesConfig.HEADER_COLOR.get(), ChatFormatting.GOLD);
ChatFormatting bodyColor = parseColor(ServerRulesConfig.BODY_COLOR.get(), ChatFormatting.YELLOW);
// Add helper method:
private static ChatFormatting parseColor(String name, ChatFormatting fallback) {
try {
return ChatFormatting.valueOf(name.toUpperCase());
} catch (IllegalArgumentException e) {
return fallback;
}
}
```
For emoji stripping, gate `convertEmojis()` on the config value:
```java
String processedText = ServerRulesConfig.STRIP_EMOJIS.get()
? convertEmojis(rawDiscordText)
: rawDiscordText;
```
### 6. ServerRules.java — update mod ID references
- Any string `"serverrules"``"discordrules"`
- Any string `"Firefrost"` or `"FireFrost"` in user-visible text → remove or make generic
---
## Build Instructions
Same as Task #136 — use the Dev Panel build environment:
```bash
# Java 21 for 1.21.1
use-java 21
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.21.1
/opt/gradle-8.8/bin/gradle build
# Java 17 for 1.20.1
use-java 17
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.20.1
/opt/gradle-8.8/bin/gradle build
# Java 8 for 1.16.5
use-java 8
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.16.5
/opt/gradle-7.6.4/bin/gradle build
```
Output jars go in `build/libs/` — grab the one without `-sources` or `-dev`.
---
## Deliverable
3 built jars in `services/discord-rules/`:
- `discord-rules-1.0.0-1.21.1-neoforge.jar`
- `discord-rules-1.0.0-1.20.1-forge.jar`
- `discord-rules-1.0.0-1.16.5-forge.jar`
Commit source + jars to `firefrost-services` main branch, then update `ACTIVE_CONTEXT.md`.
---
## Notes
- Don't touch `services/rules-mod/` — that stays as Firefrost's internal version
- Default colors GOLD/YELLOW chosen to match Firefrost's visual identity (subtle brand nod)
- MIT license — maximizes community adoption
- Chronicler is writing the CurseForge project page copy in parallel
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,19 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Phase 11 Prerequisites — Need credentials and env values before Code can begin
## 1. Current State
Phase 11 spec is archived and understood. Code is ready to begin building the licensing system (database migration, Arbiter API routes, Blueprint extension changes).
## 2. The Blocker
Cannot start implementation without infrastructure values that only the Chronicler (via Command Center access) can provide.
## 3. Questions for Chronicler
1. **PostgreSQL credentials** — What are the connection details for arbiter_db on Command Center? (host, port, user, password — or confirm it's 127.0.0.1 with the existing Arbiter DB user)
2. **Existing Arbiter DB schema** — Can you dump the current table list from arbiter_db so I know what's already there? (`\dt` output)
3. **BuiltByBit resource IDs** — Have the two listings (Standard / Professional) been created yet? If so, what are BBB_STANDARD_RESOURCE_ID and BBB_PRO_RESOURCE_ID? If not, should Code proceed with placeholder values?
4. **Arbiter .env current state** — What env vars does Arbiter already have? (So I don't overwrite anything when adding the new ones)
5. **Discord role + channel setup** — Has the "ModpackChecker Customer" role been created on the Firefrost Discord? Does the bot have permission to assign roles?
6. **Deployment readiness** — Once Code writes the migration SQL and API routes, should I commit and push for Chronicler to deploy? Or is there a staging step first?

View File

@@ -0,0 +1,39 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Deploy Phase 11A — MVC licensing migration + Arbiter routes
## 1. Current State
Phase 11A code is committed and pushed to `main` (fd50009). Migration SQL and API routes are ready.
## 2. The Blocker
Code cannot deploy to Command Center — Chronicler handles Arbiter deployments via Trinity Core.
## 3. Deployment Steps for Chronicler
1. Run migration on Command Center:
```
psql -U arbiter -d arbiter_db -f services/arbiter-3.0/migrations/138_mvc_licensing.sql
```
2. Deploy Arbiter code (standard pattern):
- Clone to /tmp
- Copy to /opt/arbiter-3.0
- Restart Arbiter service
- Verify health endpoint
3. Add these env vars to Arbiter .env (placeholders until BBB listings go live):
```
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
```
4. Verify new endpoints respond:
```
curl https://discord-bot.firefrostgaming.com/api/mvc/latest-version
```
Expected: `{"version":"1.0.0","download_url":"..."}`
## 4. After Deployment
Once confirmed, Code will proceed to Phase 11B/C (Discord /verify-mvc command + customer role).

View File

@@ -0,0 +1,35 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Deploy Phase 11B/C — /verify-mvc command + Discord role
## 1. Current State
Phase 11B/C code pushed (7c58cea). New `/verify-mvc` slash command ready. Needs Arbiter restart + Discord role creation.
## 2. The Blocker
Code cannot create Discord roles or restart Arbiter on Command Center.
## 3. Deployment Steps for Chronicler
1. **Create Discord role** — "ModpackChecker Customer" on Firefrost Gaming guild
- Color: whatever fits the brand (suggestion: green or teal)
- No special permissions needed — it's just a tag/gating role
- Note the role ID
2. **Update Arbiter .env** on Command Center:
```
MVC_CUSTOMER_ROLE_ID=<the role ID from step 1>
```
3. **Deploy Arbiter code** (standard rsync pattern):
- New file: `src/discord/verifymvc.js`
- Modified: `src/discord/events.js`, `src/index.js`
- Restart service
4. **Verify:**
- Health check: `curl https://discord-bot.firefrostgaming.com/health`
- Check bot logs for "Successfully reloaded application (/) commands"
- Test `/verify-mvc` appears in Discord command list
## 4. After Deployment
Code proceeds to Phase 11D — Blueprint extension license activation UI, phone-home cron, and tier gating.

View File

@@ -0,0 +1,30 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Debug dashboard badge injection — need ServerRow.tsx contents
## 1. Current State
UpdateBadge.tsx is copied correctly, but the sed injection into ServerRow.tsx may not be matching. Need to see what's actually on disk.
## 2. Questions — Please Run These on Dev Panel
```bash
# 1. Does ServerRow.tsx exist?
find /var/www/pterodactyl/resources/scripts -name "ServerRow.tsx" 2>/dev/null
# 2. Is UpdateBadge import present?
grep -n "UpdateBadge" /var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null
# 3. What does the server name line look like?
grep -n "server.name" /var/www/pterodactyl/resources/scripts/components/dashboard/ServerRow.tsx 2>/dev/null
# 4. Was yarn build run after the injection?
ls -la /var/www/pterodactyl/public/assets/manifest.json 2>/dev/null
# 5. Any JS errors visible?
# Michael: open browser console (F12) on dashboard, look for red errors
```
The sed pattern `{server.name}</p>` has to match exactly. If the actual JSX is different (e.g. `{server.name}</span>` or uses a different variable), the injection silently fails.
Also: **did `yarn build:production` run after build.sh?** The React components need to be compiled for the frontend to pick them up.

View File

@@ -0,0 +1,25 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Dashboard badges blocked by Blueprint CSS module build errors — decision needed
## 1. Current State
Badge injection into ServerRow.tsx is correct. But `yarn build:production` fails on 7 pre-existing CSS module errors from Blueprint beta — not caused by our code.
## 2. The Blocker
Blueprint beta-2026-01's webpack config doesn't have a CSS module loader configured. This breaks `yarn build:production` for any panel with Blueprint installed. Our badge code can't compile.
## 3. Decision for Michael
**Option A: Skip badges for v1.0.0 launch** (recommended)
- Admin page + console widget both work
- Badges are nice-to-have, not core functionality
- Add badges in v1.1.0 when Blueprint fixes their build or we find a workaround
- Zero risk to launch timeline
**Option B: Patch webpack config**
- Add `css-loader` with modules support to Blueprint's webpack
- Risk: could break other Blueprint components, and gets overwritten on Blueprint updates
- Not recommended 52 hours before launch
Which way do you want to go?

View File

@@ -0,0 +1,64 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Deploy Phase 11D to Dev Panel — Blueprint license integration
## 1. Current State
Phase 11D code pushed (8872f67). Blueprint extension has license activation UI, phone-home cron, and tier gating. Ready for testing on Dev Panel.
## 2. Deploy Target
**Dev Panel (64.50.188.128)** — NOT live Panel VPS.
## 3. Files Changed
**New files:**
- `blueprint-extension/app/Services/LicenseService.php`
- `blueprint-extension/app/Console/Commands/ValidateLicense.php`
**Modified files:**
- `blueprint-extension/admin/controller.php` — added LicenseService DI, license activation/deactivation handling
- `blueprint-extension/admin/view.blade.php` — license UI, status banners, dynamic tier gating
## 4. Dependencies
- No new Composer packages — uses Laravel's built-in `Http` facade (already available)
- No database migrations needed (license state stored in Blueprint's dbGet/dbSet)
## 5. Deployment Steps
1. Rebuild the .blueprint package:
```bash
cd /opt/mod-builds/firefrost-services/services/modpack-version-checker/blueprint-extension
bash build.sh
```
2. Install on Dev Panel:
```bash
blueprint -install modpackchecker
```
3. Register the daily cron (add to Panel's crontab):
```
0 4 * * * cd /var/www/pterodactyl && php artisan mvc:validate >> /dev/null 2>&1
```
4. For immediate testing, run manually:
```bash
cd /var/www/pterodactyl && php artisan mvc:validate
```
## 6. What "Working" Looks Like
1. **Admin page loads** — visit `/admin/extensions/modpackchecker`, no errors
2. **License section visible** — "BuiltByBit Order ID" text input + gray "Not Activated" badge
3. **Activate test** — enter a test order ID, click Save. Should show error "License not found" (expected — no real license exists yet). Confirms Arbiter connectivity.
4. **Pro fields locked** — Check Interval dropdown and Discord Webhook URL should be disabled (grayed out)
5. **Artisan command runs** — `php artisan mvc:validate` outputs "No license configured" (expected for fresh install)
6. **No PHP errors in logs** — `tail -f /var/www/pterodactyl/storage/logs/laravel.log`
## 7. Smoke Test with Real License
To fully test the flow, Chronicler can provision a test license directly in the DB:
```sql
INSERT INTO mvc_licenses (order_id, buyer_id, tier, max_activations)
VALUES ('TEST-001', 'chronicler', 'professional', 5);
```
Then enter `TEST-001` in the admin UI — should activate, show green badge, unlock pro fields.

View File

@@ -0,0 +1,23 @@
# Architectural Request
**Date:** 2026-04-12
**Topic:** Phase 11E scope — what does the GitBook knowledge base need?
## 1. Current State
Phases 11A11D complete. Phase 11E is "GitBook knowledge base migration" but the spec doesn't detail what content is needed or where GitBook is hosted.
## 2. Questions for Chronicler
1. **Is there an existing GitBook instance?** If so, what's the URL and how does Code push content to it? (API? Git repo? Manual paste?)
2. **Or is this Markdown docs that get uploaded to GitBook later?** If so, Code can write the docs as Markdown files in the repo and Chronicler handles the GitBook publishing.
3. **What pages are needed?** Suggested structure:
- Getting Started (installation, activation)
- Configuration (CurseForge API key, egg variables, file detection)
- Dashboard Badge (how it works, what the colors mean)
- Console Widget (manual checks, rate limits)
- Professional Tier Features (auto-cron, Discord webhooks, FTB)
- Troubleshooting (common errors, support contact)
- License & Activation (how to activate, deactivation, grace period)
4. **Should Code write these docs now, or skip to 11F (BuiltByBit listing) which is more launch-critical?**
54 hours to launch — happy to prioritize whatever moves the needle most.

View File

@@ -0,0 +1,323 @@
# Feature Request: Arbiter Native Discord Role Management
**Date:** 2026-04-13
**Topic:** Replace Carlbot with native Arbiter button roles, welcome messages, and automated get-roles message lifecycle
**Priority:** POST-LAUNCH — build and test in parallel with Carlbot, cut over week of April 20
**Filed by:** Chronicler #86
---
## Background
Carlbot currently handles three things for Firefrost Gaming:
1. Welcome messages on member join
2. Reaction roles in #get-roles
3. Wanderer role assignment on join
Every time a Minecraft server is created (/createserver) or deleted (/delserver), staff must manually update Carlbot's reaction role config and add/remove emoji from the #get-roles message. This is error-prone and completely automatable.
**Goal:** Arbiter owns all three functions natively. When Pterodactyl fires a server lifecycle webhook, the #get-roles message updates automatically — no manual steps.
**Architecture review:** Two rounds of Gemini consultation completed.
- docs/consultations/gemini-arbiter-discord-roles-round-1-2026-04-13.md (in ops manual)
- docs/consultations/gemini-arbiter-discord-roles-round-2-2026-04-13.md (in ops manual)
---
## 1. New File: src/discord/client.js
Module-level singleton for the discord.js Client. Shared across the entire app.
```javascript
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
]
});
client.once('ready', () => {
console.log(`[Discord] Gateway connected as ${client.user.tag}`);
});
module.exports = client;
```
---
## 2. Modify: src/index.js (startup)
Start Express and Discord concurrently — do NOT block Express on Discord ready.
```javascript
const client = require('./discord/client');
const discordEvents = require('./discord/events');
discordEvents.register(client);
app.listen(PORT, () => {
console.log(`[Arbiter] Express listening on port ${PORT}`);
});
client.login(process.env.DISCORD_BOT_TOKEN).catch(err => {
console.error('[Discord] Gateway login failed:', err);
});
```
If a Pterodactyl webhook fires before Gateway is ready, check `client.isReady()` and return 503 if not.
---
## 3. New File: src/discord/events.js
```javascript
const { sendWelcomeMessage } = require('./welcome');
const { handleInteraction } = require('./interactions');
function register(client) {
client.on('guildMemberAdd', async (member) => {
if (process.env.WELCOME_MESSAGES_ENABLED !== 'true') return;
await sendWelcomeMessage(member);
});
client.on('interactionCreate', async (interaction) => {
await handleInteraction(interaction);
});
}
module.exports = { register };
```
---
## 4. New File: src/discord/welcome.js
```javascript
const WELCOME_CHANNEL_ID = process.env.DISCORD_WELCOME_CHANNEL_ID;
async function sendWelcomeMessage(member) {
try {
const channel = await member.guild.channels.fetch(WELCOME_CHANNEL_ID);
if (!channel) return;
await channel.send({
embeds: [{
title: `Welcome to Firefrost Gaming, ${member.user.username}! 🔥❄️`,
description: `Head to <#${process.env.DISCORD_GET_ROLES_CHANNEL_ID}> to grab your server roles!`,
color: 0xFF6B35,
thumbnail: { url: member.user.displayAvatarURL() },
timestamp: new Date().toISOString(),
}]
});
} catch (err) {
console.error('[Welcome] Failed to send welcome message:', err);
}
}
module.exports = { sendWelcomeMessage };
```
---
## 5. New File: src/discord/getRolesMessage.js
Manages the persistent #get-roles button message. Called by webhook.js on /createserver and /delserver.
```javascript
const client = require('./client');
const db = require('../db');
const GET_ROLES_CHANNEL_ID = process.env.DISCORD_GET_ROLES_CHANNEL_ID;
async function updateGetRolesMessage(servers) {
if (!client.isReady()) {
console.warn('[GetRoles] Discord client not ready — skipping');
return;
}
const channel = await client.channels.fetch(GET_ROLES_CHANNEL_ID);
const embed = buildEmbed(servers);
const components = buildButtons(servers);
const storedMessageId = await db.getSetting('discord_get_roles_message_id');
if (storedMessageId) {
try {
await channel.messages.edit(storedMessageId, { embeds: [embed], components });
return;
} catch (err) {
if (err.code !== 10008) throw err;
console.warn('[GetRoles] Stored message not found, reposting...');
}
}
const message = await channel.send({ embeds: [embed], components });
await db.setSetting('discord_get_roles_message_id', message.id);
}
function buildEmbed(servers) {
return {
title: '🎮 Choose Your Servers',
description: servers.length > 0
? 'Click a button to get access to a server channel. Click again to remove it.'
: 'No servers are currently active.',
color: 0x4ECDC4,
footer: { text: 'Firefrost Gaming — Role Assignment' },
timestamp: new Date().toISOString(),
};
}
function buildButtons(servers) {
if (servers.length === 0) return [];
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const rows = [];
let currentRow = new ActionRowBuilder();
let count = 0;
for (const server of servers) {
if (count > 0 && count % 5 === 0) {
rows.push(currentRow);
currentRow = new ActionRowBuilder();
}
currentRow.addComponents(
new ButtonBuilder()
.setCustomId(`toggle_role_${server.roleId}`)
.setLabel(server.name)
.setStyle(ButtonStyle.Secondary)
.setEmoji(server.emoji || '🎮')
);
count++;
}
if (count % 5 !== 0 || count === 0) rows.push(currentRow);
return rows;
}
module.exports = { updateGetRolesMessage };
```
---
## 6. New File: src/discord/interactions.js
```javascript
async function handleInteraction(interaction) {
if (!interaction.isButton()) return;
if (interaction.customId.startsWith('toggle_role_')) {
await handleRoleToggle(interaction);
}
}
async function handleRoleToggle(interaction) {
await interaction.deferReply({ ephemeral: true });
const roleId = interaction.customId.replace('toggle_role_', '');
const member = interaction.member;
const guild = interaction.guild;
try {
const role = await guild.roles.fetch(roleId);
if (!role) {
await interaction.editReply({ content: '⚠️ Role not found. Please contact an admin.' });
return;
}
const hasRole = member.roles.cache.has(roleId);
if (hasRole) {
await member.roles.remove(role);
await interaction.editReply({ content: `✅ Removed the **${role.name}** role.` });
} else {
await member.roles.add(role);
await interaction.editReply({ content: `✅ You now have the **${role.name}** role!` });
}
} catch (err) {
console.error('[Interactions] Role toggle failed:', err);
await interaction.editReply({ content: '⚠️ Something went wrong. Please try again.' });
}
}
module.exports = { handleInteraction };
```
---
## 7. Modify: src/routes/webhook.js
After successful server creation or deletion, call updateGetRolesMessage with the current active server list.
---
## 8. Database: Settings Table
```sql
CREATE TABLE IF NOT EXISTS settings (
key VARCHAR(255) PRIMARY KEY,
value TEXT
);
```
Add getSetting/setSetting helpers to DB module.
---
## 9. New .env Variables
```
DISCORD_GET_ROLES_CHANNEL_ID=1403980899464384572
DISCORD_WELCOME_CHANNEL_ID=1403980049530490911
WELCOME_MESSAGES_ENABLED=false
```
---
## 10. Server Role ID Reference
| Server Name | Role ID |
|-------------|---------|
| All The Mods: To the Sky | 1491028496284258304 |
| Stoneblock 4 | 1491028769132253274 |
| Society: Sunlit Valley | 1491028885981102270 |
| All The Mons | 1491029000108380170 |
| Mythcraft 5 | 1491029070190870548 |
| Beyond Depth | 1491029215963906149 |
| Beyond Ascension | 1491029284159094904 |
| Wold's Vaults | 1491029373640376330 |
| Otherworld [Dungeons & Dragons] | 1491029454011629749 |
| DeceasedCraft | 1491029615739801800 |
| Submerged 2 | 1491029708878647356 |
| Sneak's Pirate Pack | 1491029809273508112 |
| Cottage Witch | 1491029870002569298 |
| Homestead | 1491030015746510939 |
| Farm Crossing 6 | 1493352900997415134 |
---
## 11. Carlbot Cutover Sequence (Week of April 20)
DO NOT execute before April 15 launch.
1. Confirm Arbiter role system verified in test channel
2. Deploy final build to production
3. Disable Carlbot Reaction Roles for #get-roles in Carlbot dashboard
4. Delete old Carlbot #get-roles message
5. Trigger updateGetRolesMessage() to post new button message
6. Disable Carlbot Welcome module
7. Set WELCOME_MESSAGES_ENABLED=true in .env
8. Restart arbiter-3
9. Verify welcome fires on test join
10. Remove Carlbot from server
---
## Rate Limit Handling
Native retry on 429. No queue needed at current scale.
---
## Notes for Code
- Feature flag (WELCOME_MESSAGES_ENABLED) is critical — don't skip it
- 404 fallback in updateGetRolesMessage is important — admins will delete that message
- Keep Discord client strictly in src/discord/
- testserver role (1491487727928217815) exists — do NOT include in button list
- This is post-launch work — no rush, but nice change from MVC 😄

View File

@@ -0,0 +1,45 @@
# REQ-2026-04-13-missing-modpack-installations-migration
**Filed by:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — blocks Dev Panel deploy, will block any fresh install
**Status:** OPEN
## Problem
The `modpack_installations` table exists on live panel (panel-vps) but has **no migration file** in the repo. It was created directly on the server at some point and never committed.
This caused an API error on Dev Panel during today's deploy — the table simply didn't exist. Chronicler manually created it via raw SQL as a temporary fix to unblock testing.
## Live Panel Schema (confirmed via Trinity Core)
```
Field | Type | Null | Key | Default | Extra
provider | varchar(191) | NO | | NULL |
modpack_id | varchar(191) | NO | | NULL |
server_id | bigint(20) unsigned | NO | PRI | NULL |
finalized | tinyint(1) | NO | | 0 |
```
PRIMARY KEY on `server_id`. Engine InnoDB, charset utf8mb4.
## What Code Needs to Do
Create a proper Laravel migration file:
```
services/modpack-version-checker/blueprint-extension/database/migrations/2026_04_13_000001_create_modpack_installations_table.php
```
Using the schema above. Standard Blueprint/Laravel migration format matching the existing migration files in that directory.
## Why It Matters
Without this, any fresh Blueprint install (new panel, wipe and reinstall) will throw:
`SQLSTATE[42S02]: Table 'panel.modpack_installations' doesn't exist`
This will affect every BuiltByBit customer who purchases ModpackChecker.
## After Code Pushes
Chronicler will pull on Dev Panel and confirm the migration file is present before live panel deploy.

View File

@@ -0,0 +1,7 @@
# Bug Fix + Feature Request: Rules Mod Config Reset + Versioning
**Date:** 2026-04-13
**Topic:** Fix Forge config reset on startup, introduce version control
**Priority:** HIGH — rules mod broken on Otherworld
**Filed by:** Chronicler #86
**Status:** RESOLVED in v1.0.2

View File

@@ -0,0 +1,74 @@
# Bug Follow-up v2: COMMON Config File Not Read at Runtime
**Date:** 2026-04-13
**Topic:** firefrostrules-common.toml has correct values but mod still serves default rules
**Priority:** HIGH — Otherworld still broken
**Filed by:** Chronicler #86
**Related to:** REQ-2026-04-13-rules-mod-config-still-resetting.md
---
## What We Did
1. Deployed firefrostrules-1.0.2-1.20.1-forge.jar to Otherworld (NC1, volume d4790f45)
2. Deleted old world/serverconfig/firefrostrules-server.toml
3. Started server — generated config/firefrostrules-common.toml with defaults
4. Stopped server — wrote real values to config/firefrostrules-common.toml
5. Restarted — config file still has correct values (confirmed)
6. Restarted again — config file still has correct values (confirmed)
Config persistence is fixed. ✅
## What's Still Wrong
Player ran `/rules` and got the hardcoded default rules, not Discord content.
Log evidence:
```
[22:42:38] [modloading-worker-0/INFO] [com.firefrostgaming.rules.ServerRules/]: Firefrost Rules Mod Initialized.
[22:43:08] [main/INFO] [com.firefrostgaming.rules.ServerRules/]: Registered /rules command.
[22:43:58] [Server thread/INFO] [minecraft/MinecraftServer/]: [Fire] Server Rules
Please check Discord for the full rules list.
```
Two problems:
1. **Loading handler never fired** — Code's `onConfigLoaded` handler should have logged "Rules mod config loaded successfully. Channel: 1493228237860638770" — it's completely absent from logs. The handler is not executing.
2. **Default rules served**`/rules` returned hardcoded defaults, meaning `ServerRulesConfig.BOT_TOKEN.get()` is returning `YOUR_TOKEN_HERE` at runtime even though the file has the real token.
## What This Means
The config file has correct values but `ServerRulesConfig.SPEC` is not reading from it. The `ModConfig.Type.COMMON` registration may not be correctly linking the spec to the file on disk, or the config values are being read before the file is loaded.
## Hypothesis
In Forge 1.20.1, `ModConfig.Type.COMMON` configs are loaded from `config/<modid>-common.toml`. But the filename Forge generates may need to match exactly what the spec expects. Check:
1. What filename is Forge actually generating? Is it `firefrostrules-common.toml` or something else like `firefrostrules-server.toml` even with COMMON type?
2. Is `ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ServerRulesConfig.SPEC)` being called before or after the config file is read?
3. Is there a `ModConfigEvent.Loading` listener that confirms the spec is bound to the correct file?
## Current File State on Server
```
Path: /var/lib/pterodactyl/volumes/d4790f45-b1bc-43b8-98c4-425238387ee3/config/firefrostrules-common.toml
bot_token = "MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI"
channel_id = "1493228237860638770"
message_id = "1493228916998013101"
```
File is correct. Mod is not reading it.
## What Code Needs to Do
1. Add a debug log immediately after `ServerRulesConfig.BOT_TOKEN.get()` in `RulesCommand.java` to print the actual value being read at command execution time
2. Verify the `ModConfigEvent.Loading` listener is actually being registered and firing
3. Check if there's a timing issue — config might need to be read after `FMLServerStartedEvent` rather than at command time
4. Verify COMMON type generates correct filename in 1.20.1
## Applies To
1.20.1 confirmed broken. 1.21.1 and 1.16.5 status unknown — may have same issue.
Bump to 1.0.3 once fixed. Update CHANGELOG.md.

View File

@@ -0,0 +1,72 @@
# Bug Follow-up v3: /rules Still Returns Hardcoded Defaults Despite Config Loading
**Date:** 2026-04-13
**Topic:** Config loads correctly (Loading handler confirmed), but /rules still returns hardcoded default text
**Priority:** HIGH — Otherworld still broken
**Filed by:** Chronicler #86
**Related to:** REQ-2026-04-13-rules-mod-config-not-read-at-runtime.md
---
## Current State
v1.0.3 deployed to Otherworld. Config loading confirmed:
```
[22:59:13] [main/INFO] [com.firefrostgaming.rules.ServerRules/]: Rules mod config loaded successfully. Channel: 1493228237860638770
```
Config file on disk has correct values. Survives restarts. ✅
## What's Still Wrong
`/rules` run twice after server fully loaded — both return hardcoded defaults:
```
[23:00:54] [Server thread/INFO] [minecraft/MinecraftServer]: [Fire] Server Rules
1. Be respectful to all players.
2. No griefing or cheating.
3. Follow staff instructions.
Please check Discord for the full rules list.
[23:01:19] Same result.
```
## What The Logs Show
No `DiscordFetcher` log output at all — no 401, no 403, no fetch attempt, no fallback warning. The fetcher is completely silent. Only the hardcoded output appears.
## What This Means
`DiscordFetcher` is not being called, OR it's being called but returning null/empty silently and falling back to defaults without logging.
Two likely causes:
**Cause A — isMessageIdValid() returning false:**
`ServerRulesConfig.isMessageIdValid()` checks that message_id matches `^\d{17,20}$`. If the config value is being read with surrounding quotes or whitespace, the regex fails and the command returns defaults without attempting the fetch.
Add a debug log immediately in `RulesCommand.java` before the validity check:
```java
LOGGER.info("Rules command: token={}, channel={}, messageId={}",
ServerRulesConfig.BOT_TOKEN.get(),
ServerRulesConfig.CHANNEL_ID.get(),
ServerRulesConfig.MESSAGE_ID.get());
LOGGER.info("isMessageIdValid={}", ServerRulesConfig.isMessageIdValid());
```
**Cause B — DiscordFetcher silently returning null:**
The async fetch may be completing but returning null or empty string, and `RulesCommand` falls back to defaults without logging. Add explicit logging in `DiscordFetcher` for every code path — success, failure, null return.
## What Code Needs To Do
1. Add the debug log in `RulesCommand.java` showing actual runtime values of all three config fields
2. Add explicit logging in `DiscordFetcher` for: fetch attempt started, HTTP status received, response body preview, null/empty result
3. Bump to v1.0.4, push
Chronicler will pull v1.0.4, deploy to Otherworld, and report what the new log lines show.
## Server Info
- Otherworld, NC1, volume `d4790f45-b1bc-43b8-98c4-425238387ee3`
- Mod version: firefrostrules-1.0.3-1.20.1-forge.jar
- Config: `/var/lib/pterodactyl/volumes/d4790f45-b1bc-43b8-98c4-425238387ee3/config/firefrostrules-common.toml`

View File

@@ -0,0 +1,51 @@
# REQ-2026-04-13-status-route-mismatch
**Filed by:** Chronicler #85
**Date:** 2026-04-13
**Priority:** HIGH — widget does not auto-load on page open
**Status:** OPEN
## Problem
The widget's `useEffect` fires on page load and calls:
```
GET /api/client/extensions/modpackchecker/servers/{uuid}/status
```
But the registered route is:
```
GET /api/client/extensions/modpackchecker/status
```
Result: `useEffect` hits a 404 on load → `catch(() => setData(null))` fires → widget shows nothing. Only the manual refresh button works because it hits `/check` (POST) which IS registered correctly.
## Evidence
Route list from Dev Panel:
```
GET|HEAD api/client/extensions/modpackchecker/status ModpackAPIController@...
POST api/client/extensions/modpackchecker/servers/{server}/check ModpackAPIController@...
```
Widget call in `wrapper.tsx` line 46:
```js
http.get(`/api/client/extensions/modpackchecker/servers/${uuid}/status`)
```
## What Needs to Fix
Either:
- **Option A:** Update the route registration to match the widget: `servers/{server}/status`
- **Option B:** Update the widget's `useEffect` to call the existing route (passing uuid as a query param)
Option A is cleaner — keeps the route pattern consistent with `/check` and future endpoints.
## Files Involved
- Route registration: check `routes/` directory in blueprint extension
- Controller: `app/Http/Controllers/ModpackAPIController.php` — status method may need `$server` parameter
- Widget: `views/server/wrapper.tsx` line 46 (may not need changes if Option A)
## After Code Pushes
Chronicler will pull, redeploy PHP + routes to Dev Panel, verify widget auto-loads on page open.

View File

@@ -0,0 +1,57 @@
# Architectural Request
**Date:** 2026-04-13
**Topic:** Consolidated v1.1.0 deploy — all 5 priorities complete
## 1. What Changed
### New Migration
- `2026_04_13_000000_add_file_id_and_ignored.php` — adds `current_file_id`, `latest_file_id`, `is_ignored`
- `2026_04_12_000000_add_detection_columns.php` — adds `detection_method`, `is_user_overridden`
### Modified PHP Files
- `CheckModpackUpdates.php` — hybrid detection (modpack_installations → egg → file → BCC log), file ID comparison, date-time seeding, is_ignored skip
- `ModpackApiService.php` — all platforms return `file_id`, new `fetchFileHistory()` for CurseForge/Modrinth/FTB
- `ModpackAPIController.php` — 4 new endpoints (serverStatus, releases, calibrate, toggleIgnore), modpack_installations in manualCheck
- `routes/client.php` — 4 new routes
### Modified TSX Files
- `views/server/wrapper.tsx` — zero-click widget with recalibrate dropdown + ignore button
## 2. Deployment Steps
```bash
# 1. Pull latest
cd /path/to/firefrost-services && git pull
# 2. Copy PHP files
cp blueprint-extension/app/Console/Commands/CheckModpackUpdates.php /var/www/pterodactyl/app/Console/Commands/
cp blueprint-extension/app/Services/ModpackApiService.php /var/www/pterodactyl/app/Services/
cp blueprint-extension/app/Http/Controllers/ModpackAPIController.php /var/www/pterodactyl/app/Http/Controllers/
cp blueprint-extension/routes/client.php [blueprint routes path]
# 3. Run migrations
cd /var/www/pterodactyl && php artisan migrate
# 4. Rebuild Blueprint (copies TSX + runs yarn build)
blueprint -install modpackchecker
# 5. Clear caches
php artisan optimize:clear
# 6. Test
php artisan modpackchecker:check
```
## 3. Verification
1. `php artisan modpackchecker:check` — should detect 19+ servers via modpack_installations
2. Dashboard badges show on server cards
3. Console widget loads automatically (no click needed)
4. Console widget shows version comparison (current → latest)
5. Calibrate button shows release dropdown
6. Ignore button hides non-modpack servers
## 4. Deploy to Both Panels
- Dev Panel (64.50.188.128) first
- Live Panel (45.94.168.138) after verification

View File

@@ -0,0 +1,126 @@
# Code Request — Trinity Console PWA Phase 1
**Filed by:** Chronicler #88
**Date:** 2026-04-14
**Priority:** Medium
**Task DB:** task_number 117
---
## What To Build
Make Trinity Console installable as a Progressive Web App. Phase 1 only —
foundation that enables "Add to Home Screen" on Android and iOS.
---
## Files To Create
### 1. `src/public/manifest.json`
```json
{
"name": "Trinity Console",
"short_name": "Trinity",
"description": "Firefrost Gaming Operations Console",
"start_url": "/admin",
"display": "standalone",
"background_color": "#1a1a1a",
"theme_color": "#06b6d4",
"orientation": "portrait-primary",
"icons": [
{
"src": "/images/trinity-icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/trinity-icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
```
### 2. `src/public/sw.js` (Service Worker)
Cache static assets only. Never cache admin API responses or HTMX partials.
```javascript
const CACHE_NAME = 'trinity-console-v1';
const STATIC_ASSETS = [
'/css/app.css',
'/manifest.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
);
});
self.addEventListener('fetch', event => {
// Only cache GET requests for static assets
if (event.request.method !== 'GET') return;
if (event.request.url.includes('/admin/')) return; // Never cache admin routes
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
});
```
### 3. Icon placeholders
Create simple placeholder PNGs at:
- `src/public/images/trinity-icon-192.png`
- `src/public/images/trinity-icon-512.png`
Use the cyan (#06b6d4) Firefrost color with a simple ❄️ or "T" — we'll replace
with proper branding later.
---
## Files To Modify
### `src/views/layout.ejs` — Add to `<head>`:
```html
<!-- PWA -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#06b6d4">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Trinity">
<link rel="apple-touch-icon" href="/images/trinity-icon-192.png">
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(err => console.log('SW registration failed:', err));
});
}
</script>
```
Make sure `<meta name="viewport" content="width=device-width, initial-scale=1">`
exists in the head (add if missing).
---
## Notes
- Phase 2 (mobile layout) and Phase 3 (push notifications) are separate tasks
- Icons are placeholders — proper branding assets come later
- The service worker should be conservative — cache as little as possible to
avoid stale admin data
- Test by opening Trinity Console in Chrome mobile → three-dot menu →
"Add to Home Screen"
---
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,78 @@
# Code Request — Rules Mod 1.18.2 Build
**Filed by:** Chronicler #88
**Date:** 2026-04-14
**Priority:** Medium
**Target:** Both `rules-mod` (Firefrost internal) and `discord-rules` (CurseForge generic)
---
## Background
We have the rules mod built for 1.16.5, 1.20.1, and 1.21.1. DeceasedCraft runs
Forge 1.18.2-40.2.4 and needs it too. Build a 1.18.2 version of both variants.
---
## What To Build
Backport the current 1.20.1 version of both mods to 1.18.2. Use the 1.20.1 source as the base for both.
### 1. `services/rules-mod/1.18.2/` — Firefrost internal version
- Backport from `services/rules-mod/1.20.1/`
- Target: Forge 1.18.2-40.2.4
- Java version: **Java 17**
- Output jar: `firefrostrules-1.0.5-1.18.2-forge.jar`
### 2. `services/discord-rules/1.18.2/` — CurseForge generic version
- Backport from `services/discord-rules/1.20.1/`
- Same Forge/Java targets as above
- Output jar: `discord-rules-1.0.5-1.18.2-forge.jar`
---
## Build Instructions
```bash
use-java 17
cd /opt/mod-builds/firefrost-services/services/rules-mod/1.18.2
/opt/gradle-8.8/bin/gradle build
cd /opt/mod-builds/firefrost-services/services/discord-rules/1.18.2
/opt/gradle-8.8/bin/gradle build
```
Output jars go in `build/libs/` — grab the one without `-sources` or `-dev`.
---
## Deliverable
2 built jars committed to `firefrost-services` main:
- `services/rules-mod/discord-rules-1.0.5-1.18.2-forge.jar`
- `services/discord-rules/discord-rules-1.0.5-1.18.2-forge.jar`
Deploy `firefrostrules-1.0.5-1.18.2-forge.jar` to DeceasedCraft:
- Server UUID: `8950fa1e-acd6-4db9-9595-076007cc26af`
- Node: NC1
- Mods path: `/var/lib/pterodactyl/volumes/8950fa1e-acd6-4db9-9595-076007cc26af/mods/`
- Config path: `world/serverconfig/firefrostrules-server.toml`
After deploy, update `ACTIVE_CONTEXT.md`.
---
## Config for DeceasedCraft After Deploy
```toml
bot_token = "MTQ4NzA4MDE2Njk2OTU3NzUwMg.GU5EsT.mqBwo7XUHsciN9jNy9OygTRkaMZ9qJ2tHw7HbI"
channel_id = "DECEASEDCRAFT_RULES_CHANNEL_ID"
message_id = "DECEASEDCRAFT_RULES_MESSAGE_ID"
```
Michael needs to provide the channel_id and message_id from Discord — the
#server-rules-for-bot channel message for DeceasedCraft.
---
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,128 @@
# Code Request — Task Module Improvements
**Filed by:** Chronicler #88
**Date:** 2026-04-14
**Priority:** Medium
**Task DB:** task_number 105 (Trinity Console Review Workflow — repurpose or create new)
---
## Background
The current task module at `/admin/tasks` shows a flat list. Michael wants
significant UX improvements. This request covers everything Code can build
without further architecture decisions.
---
## Feature 1: Click Task → Slide-Out Detail Panel
When clicking a task name, show a slide-out panel (right side) with:
- Full title, status badge, priority badge, owner
- Full description (rendered as markdown)
- Tags
- Created/updated/completed timestamps
- Completed by
- spec_path (if set)
- Close button (X or click outside)
Do NOT navigate away from the list — panel overlays on top.
Use a simple CSS transition (translate from right). No external libraries needed.
---
## Feature 2: Sorting
Add sort controls above the task list:
```
Sort by: [Number ▼] [Priority] [Status] [Updated]
```
Default: task_number ASC. Clicking active sort toggles ASC/DESC.
Persist sort preference in localStorage.
---
## Feature 3: Filter Chips
Add filter chips row above the list:
**Status:** All | Open | In Progress | Blocked | Done | Obsolete
**Priority:** All | Critical | High | Medium | Low | Wish
Chips are toggleable. Multiple status/priority selections allowed.
Active chips highlighted in cyan.
---
## Feature 4: Saved Filter Presets
Add a "Presets" dropdown with these saved filters:
- **Launch Fires** — status=open, priority=high OR critical
- **Code Queue** — tags contains 'code' OR title contains 'Code'
- **Post-Launch** — status=open, priority=low OR wish
- **All Open** — status IN (open, in_progress, blocked)
Presets are hardcoded in the view for now (no CRUD needed yet).
---
## Feature 5: Kanban View
Add a view toggle: **[List] [Kanban]**
Kanban shows 4 columns: Open | In Progress | Blocked | Done
Cards show: task_number, title, priority badge.
Cards are NOT draggable yet (drag-to-reorder is Phase 2).
---
## Feature 6: Session Summary Badge
Add a small banner or badge at the top of the task list:
```
✅ 3 tasks completed today
```
Query: `SELECT COUNT(*) FROM tasks WHERE completed_at::date = CURRENT_DATE`
Show only if count > 0.
---
## Feature 7: Code Queue Indicator
In the nav sidebar next to "Tasks", show a badge with the count of tasks
where tags contains 'code' AND status IN ('open', 'in_progress').
Query:
```sql
SELECT COUNT(*) FROM tasks
WHERE 'code' = ANY(tags) AND status IN ('open', 'in_progress')
```
Badge style: small cyan circle with white number, same as Discord notification badges.
---
## API Notes
All data comes from existing `/admin/tasks` route. Check what data is already
passed to the view and add any missing fields (description, tags, completed_by,
spec_path) to the route query if needed.
---
## Deliverable
- Updated `src/views/admin/tasks/` view(s)
- Updated `src/routes/admin/tasks.js` if additional fields needed
- No new tables required
- Update `ACTIVE_CONTEXT.md`
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,98 @@
# Code Request — Version UI in Server Matrix Body
**Filed by:** Chronicler #88
**Date:** 2026-04-14
**Priority:** Medium
**Context:** Pre-launch, defer until stable
---
## Background
Chronicler #88 added modpack version tracking infrastructure today:
- `current_version` column on `server_config`
- `server_version_history` table (version, who, when)
- `POST /admin/servers/:id/set-version` route
- `GET /admin/servers/:id/version-history` route
- Helper JS functions `saveVersion()`, `toggleVersionForm()`, `hideVersionForm()` in `servers/index.ejs`
The UI addition to `_matrix_body.ejs` kept breaking EJS due to single quotes
inside ternary expressions inside HTML attributes. The file was reverted to
clean state. This task is to add the version UI properly.
---
## What To Add
At the bottom of each server card in `_matrix_body.ejs` (there are TWO loops —
TX1 and NC1), add a version display section BEFORE the closing `</div>`.
The tricky part: avoid single quotes inside `<%= %>` tags that are inside
HTML attributes. Use a local variable assignment instead:
```ejs
<% var currentVersion = config && config.current_version ? config.current_version : null; %>
<div class="px-4 pb-4" style="border-top:1px solid #333;padding-top:10px;margin-top:4px;">
<div style="font-size:10px;color:#666;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:6px;">📦 Installed Version</div>
<div style="display:flex;gap:6px;align-items:center;" id="version-display-<%= server.identifier %>">
<span style="font-size:13px;font-weight:600;color:<%= currentVersion ? '#4ade80' : '#555' %>;" id="version-text-<%= server.identifier %>">
<%= currentVersion || 'Not set' %>
</span>
<button id="version-edit-btn-<%= server.identifier %>"
onclick="toggleVersionForm('<%= server.identifier %>')"
style="font-size:10px;background:#333;border:1px solid #555;color:#aaa;padding:2px 8px;border-radius:4px;cursor:pointer;">
✏️ Edit
</button>
</div>
<div id="version-form-<%= server.identifier %>" style="display:none;margin-top:6px;">
<div style="display:flex;gap:6px;align-items:center;">
<input type="text" id="version-input-<%= server.identifier %>"
placeholder="e.g. 1.4.2"
style="font-size:12px;background:#1a1a1a;border:1px solid #555;color:#e0e0e0;padding:4px 8px;border-radius:4px;width:140px;" />
<button onclick="saveVersion('<%= server.identifier %>')"
style="font-size:11px;background:#2563eb;color:#fff;border:none;padding:4px 10px;border-radius:4px;cursor:pointer;">Save</button>
<button onclick="hideVersionForm('<%= server.identifier %>')"
style="font-size:11px;background:#333;border:1px solid #555;color:#aaa;padding:4px 8px;border-radius:4px;cursor:pointer;">Cancel</button>
</div>
<div id="version-result-<%= server.identifier %>" style="margin-top:4px;font-size:11px;"></div>
<div style="margin-top:6px;">
<button hx-get="/admin/servers/<%= server.identifier %>/version-history"
hx-target="#version-history-<%= server.identifier %>"
hx-swap="innerHTML"
style="font-size:10px;background:transparent;border:none;color:#555;cursor:pointer;padding:0;text-decoration:underline;">View history</button>
</div>
<div id="version-history-<%= server.identifier %>" style="margin-top:4px;background:#1a1a1a;border-radius:4px;padding:4px;"></div>
</div>
</div>
```
---
## Key Rule
**Never put single quotes inside `<%= %>` tags that are inside HTML attribute values.**
Use `<% var x = ...; %>` to assign first, then `<%= x %>` in the attribute.
---
## Validation
After adding, run this on the Dev Panel to verify EJS compiles:
```bash
cd /opt/mod-builds/firefrost-services/services/arbiter-3.0
node -e "const ejs = require('ejs'); const fs = require('fs'); try { ejs.compile(fs.readFileSync('src/views/admin/servers/_matrix_body.ejs', 'utf8')); console.log('EJS OK'); } catch(e) { console.log('ERROR:', e.message); }"
```
Must print `EJS OK` before committing.
---
## Files To Edit
- `services/arbiter-3.0/src/views/admin/servers/_matrix_body.ejs`
Do NOT touch `_server_card.ejs` or `index.ejs` — those are fine.
---
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,119 @@
# REQ-2026-04-15-reaction-roles
**From:** Chronicler #92
**Date:** 2026-04-15
**Priority:** HIGH — pre-launch
**Status:** PENDING
## Summary
Add reaction role handling to Arbiter. The `#get-roles` channel has been rebuilt with bot-owned messages. Need Arbiter to handle all three reaction role sets.
## Message IDs (bot-owned, in #get-roles)
| Message ID | Purpose |
|---|---|
| `1493930565395681402` | Choose Your Path |
| `1493930595435286548` | Notification Preferences |
| `1493930614066253908` | Server Roles |
## Emoji → Role Mappings
### Message 1 — Choose Your Path
| Emoji | Role ID | Role Name |
|---|---|---|
| 🔥 | `1482490890453782612` | 🔥 Fire Path |
| ❄️ | `1482491234378448946` | ❄️ Frost Path |
### Message 2 — Notification Preferences
| Emoji | Role ID | Role Name |
|---|---|---|
| 📢 | `1491778391060381776` | Announcements |
| 🎉 | `1491778662922457199` | Events |
| 🗒️ | `1491778706312532171` | Patch Notes |
### Message 3 — Server Roles
| Emoji | Role ID | Role Name |
|---|---|---|
| 🪨 | `1491028769132253274` | Stoneblock 4 |
| 🏝️ | `1491028496284258304` | All The Mods: To the Sky |
| 🔴 | `1491029000108380170` | All The Mons |
| 🧙 | `1491029070190870548` | Mythcraft 5 |
| ⚔️ | `1491029454011629749` | Otherworld [Dungeons & Dragons] |
| 🧟 | `1491029615739801800` | DeceasedCraft |
| 🍽️ | `1493352900997415134` | Farm Crossing 6 |
| 🏡 | `1491030015746510939` | Homestead |
| 🌌 | `1491028885981102270` | Society: Sunlit Valley |
| 🌊 | `1491029215963906149` | Beyond Depth |
| ☁️ | `1491029284159094904` | Beyond Ascension |
| 🏆 | `1491029373640376330` | Wold's Vaults |
| 🤿 | `1491029708878647356` | Submerged 2 |
| 🌙 | `1491029870002569298` | Cottage Witch |
| 🌿 | `1493924685170343978` | vanilla |
## Files to Create/Modify
### 1. CREATE `src/discord/reactionRoles.js`
- Export a `REACTION_ROLE_MAP` object keyed by message ID
- Each entry maps emoji name/id → Discord role ID
- Export `handleReactionAdd(reaction, user)` and `handleReactionRemove(reaction, user)`
- Fetch partial reactions if needed (`reaction.partial`)
- Skip bot reactions
- Look up guild member and add/remove role
### 2. MODIFY `src/index.js`
- Add `GatewayIntentBits.GuildMessageReactions` to the client intents
### 3. MODIFY `src/discord/events.js`
- Import reactionRoles handlers
- Register `messageReactionAdd` and `messageReactionRemove` events on the client
## Notes
- Follow the existing pattern in events.js (clean, minimal)
- Silent-fail on missing roles/members (don't crash)
- Log adds/removes to console for debugging
- Deploy pattern: backup → clone to /tmp → copy to /opt/arbiter-3.0 → restart arbiter-3
---
## Additional Scope — Carl-bot Migration
Take over these two Carl-bot behaviors so Carl-bot can be removed.
### 4. MODIFY `src/discord/events.js`
Register `guildMemberAdd` event:
- Assign Wanderer role (`1487267974367805545`) to new member
- Send welcome DM:
```
Hey {username}! Welcome to Firefrost Gaming!
You just landed as a Wanderer — the door is open, come explore!
Quick links:
• Check out #rules first
• Say hi in #introductions
• Head to #get-roles to pick your path and grab your server channels
Questions? We're here. Welcome to the family!
```
- Silent-fail if DMs are closed
### 5. MODIFY `src/services/discordRoleSync.js` (or stripe.js)
When a subscriber reaches Awakened tier or above (i.e. after successful Stripe checkout assigns their role), send them a DM:
```
Hey {username}! You're now part of the Firefrost family! 🎉
One quick step — head to #link-your-account and use the /link command to connect your Minecraft account so we can whitelist you on our servers.
See you in-game!
```
- This should fire from the Stripe checkout flow after role assignment
- Silent-fail if DMs are closed
### Notes
- `GuildMembers` intent is already enabled — `guildMemberAdd` will work
- Check where luckpermsSync fires post-checkout — link reminder DM should fire at the same point

View File

@@ -0,0 +1,46 @@
# Chronicler Dispatch — Auto-Detection Not Implemented
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Production Issue — Found 0 servers
`php artisan modpackchecker:check` returns "Found 0 servers with modpack configuration" on the live panel. Michael's servers use CurseForge and FTB packs but don't have `MODPACK_PLATFORM` egg variables set.
## Root Cause
`CheckModpackUpdates.php` only queries servers with `MODPACK_PLATFORM` egg variable. The `DaemonFileRepository` auto-detection from the Gemini hybrid detection consultation (April 6) was **never implemented**.
## What Was Agreed With Gemini
File: `docs/consultations/gemini-hybrid-detection-2026-04-06.md`
The agreed "Magic & Manual" hybrid approach:
1. Check egg variables first (fastest)
2. If missing → use `DaemonFileRepository` to read `manifest.json` (CurseForge detection via `projectID`)
3. If missing → read `modrinth.index.json` (Modrinth)
4. If found → save to DB with `detection_method = 'file'`
5. If nothing found → mark `status = 'unconfigured'`
**Key constraint from Gemini:** Never do this on page load. Background cron only. The `DaemonFileRepository` call is a network request to Wings.
## What Needs to Be Built
Update `CheckModpackUpdates.php` to:
1. Get ALL servers (not just ones with egg variables)
2. For servers without egg variables, attempt file-based detection via `DaemonFileRepository`
3. Read `manifest.json` → extract `projectID` for CurseForge
4. Read `modrinth.index.json` → extract for Modrinth
5. FTB: check for `version.json` or similar FTB manifest file
6. Save with `detection_method = 'file'`
7. Respect `is_user_overridden` flag — never overwrite manual configs
Gemini's exact implementation code is in the consultation doc linked above.
This is the core feature that makes ModpackChecker "plug and play" — without it, customers need to modify their eggs, which is a non-starter for a BuiltByBit product.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,35 @@
# Chronicler Dispatch — current_version empty, widget shows NOT CONFIGURED
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## State of modpackchecker_servers table
```
a0efbfe8 | curseforge | up_to_date | current= | latest=FTB StoneBlock 4 1.10.0
9310d0a6 | curseforge | up_to_date | current= | latest=Society - Capital Hill - 0.20.0
82e63949 | curseforge | up_to_date | current= | latest=All the Mods 10-6.6
```
22 records created ✅ — detection working.
## Two Issues
**1. `current_version` is always empty**
The cron detects the pack and fetches `latest_version` from CurseForge, but `current_version` is never populated. Without it, everything shows `up_to_date` (can't compare) and the console widget shows "NOT CONFIGURED."
Where should `current_version` come from?
- Egg variable `MODPACK_CURRENT_VERSION`? (requires egg change — not ideal)
- A file on the server like `version.json` or `instance.json`?
- The `modpack_installations` table — does it have a version column?
**2. Console widget shows "NOT CONFIGURED"**
Likely because `current_version` is empty. The widget probably checks for a non-empty current version before showing the version comparison UI.
Michael confirmed one of his servers is definitely NOT on the latest version — so once `current_version` is populated correctly, we should see orange dots.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,35 @@
# Chronicler Dispatch — CurseForge 403: API key not being sent correctly
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Diagnosis
CurseForge API key IS valid and stored correctly. Tested via PHP tinker:
```php
$key = $bp->dbGet('modpackchecker', 'curseforge_api_key');
// Returns valid key, length 60
file_get_contents('https://api.curseforge.com/v1/mods/925200', false,
stream_context_create(['http' => ['header' => 'x-api-key: ' . $key . "\r\n"]]));
// Returns valid JSON data ✅
```
So the key works. The 403s are coming from `ModpackApiService.php` — it's not passing the key correctly to CurseForge.
## What to Check in ModpackApiService.php
1. Is it reading the key via `$bp->dbGet('modpackchecker', 'curseforge_api_key')`?
2. Is it using `x-api-key` header (NOT `Authorization: Bearer`)?
3. Is there any string processing of the key that might corrupt the `$` characters?
The CurseForge API requires `x-api-key: <key>` as the header. Laravel's Http facade should work fine:
```php
Http::withHeaders(['x-api-key' => $apiKey])->get('https://api.curseforge.com/v1/mods/' . $modpackId)
```
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,37 @@
# Chronicler Dispatch — Detection Running But Finding Nothing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Detection is now scanning all 22 servers ✅. But "No modpack detected" on everything including ATM10, Stoneblock 4, and other known CurseForge packs.
## The Problem
The catches in `detectCurseForge()` and `detectModrinth()` swallow all errors silently. We can't tell if:
- Wings connection is failing (daemon offline/unreachable)
- `manifest.json` doesn't exist at the root level
- `manifest.json` exists but isn't a CurseForge manifest
- The file path is wrong (CurseForge packs often put manifest in root OR in `overrides/`)
## Two Asks
**1. Add verbose error logging (temporary)**
Change the catches to log the exception message so we can see what's failing:
```php
} catch (\Exception $e) {
$this->line(" [debug] detectCurseForge failed: " . $e->getMessage());
}
```
**2. Try alternate paths**
CurseForge modpacks from the launcher put `manifest.json` at the pack root. But when installed on a server, it may be at:
- `manifest.json` (root) ← current
- `config/manifest.json`
- `mods/manifest.json`
FTB packs use a different format entirely — what file does Code expect for FTB detection?
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — Live Panel Deploy Complete + Syntax Fix Needed
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Live Panel Deploy: SUCCESS ✅
ModpackChecker is live on the production Panel VPS (45.94.168.138).
- Blueprint install: ✅
- yarn build:production: ✅ `webpack 5.103.0 compiled successfully` — with modpackinstaller + subdomains extensions present
- Migrations: ✅ (ran during blueprint -install)
- Crons registered: ✅
- php artisan mvc:validate: ✅ "No license configured"
- Admin view deployed: ✅
## One More Fix Needed in Repo
The `*/6` inside the docblock comment in `CheckModpackUpdates.php` line 16 is STILL in the repo — it was fixed in the build.sh copy for Dev Panel but the source file still has it. This breaks `php artisan` on some PHP versions.
The fix (already applied locally on both panels):
```php
// Change this in the docblock comment:
* 0 */6 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
// To this:
* 0 0,6,12,18 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
```
Please push the fix to the source file in the repo so future installs don't hit it.
## What Michael Sees Tomorrow
Visit `https://panel.firefrostgaming.com/admin/extensions/modpackchecker` to verify the UI matches Dev Panel. License section showing "Not Activated" ready for BuiltByBit order ID.
🔥❄️ — Chronicler #84, The Meridian

View File

@@ -0,0 +1,65 @@
# Chronicler Dispatch — manifest.json has version field on some servers
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Discovery
Mythcraft 5 HAS a `manifest.json` at `/home/container/manifest.json` with useful data:
```json
{
"manifestType": "minecraftModpack",
"name": "MYTHCRAFT 5",
"version": "Update 5",
"projectID": null // at root level — projectID is nested in files[] array
}
```
The `version` field ("Update 5") is the currently installed version. This is real data
we can use for `current_version` — not just the pack ID.
## What This Means for Detection
When `detectCurseForge()` reads `manifest.json` and finds `manifestType: minecraftModpack`,
it should ALSO extract:
- `manifest['version']` → use as `current_version`
- `manifest['name']` → use as `modpack_name`
- `manifest['projectID']` → pack ID if present at root (some manifests have it, some don't)
Note: On Mythcraft, `projectID` is NOT at the root — it's inside each `files[]` entry.
The root doesn't have a project ID. The `modpack_installations` table has it (737497).
## Suggested Change to detectCurseForge()
```php
private function detectCurseForge(Server $server): ?array
{
try {
$content = $this->fileRepository->getContent('manifest.json');
$manifest = json_decode($content, true);
if (is_array($manifest) && ($manifest['manifestType'] ?? '') === 'minecraftModpack') {
$projectId = $manifest['projectID'] ?? null;
return [
'platform' => 'curseforge',
'modpack_id' => $projectId ? (string) $projectId : null,
'name' => $manifest['name'] ?? null,
'installed_version' => $manifest['version'] ?? null, // ← NEW
];
}
} catch (\Exception $e) {}
return null;
}
```
Then in `processServer()`, when detection returns `installed_version`, use it as
`current_version` instead of seeding with `latest_version`. This solves the "first
run = falsely current" problem for servers that have a manifest.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,43 @@
# Chronicler Dispatch — manualCheck doesn't check modpack_installations
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Root Cause
The console widget calls `manualCheck()` in `ModpackAPIController.php`. That method only checks:
1. Egg variables
2. File detection (DaemonFileRepository)
It does NOT check `modpack_installations` — so it always returns "Could not detect modpack" for servers without egg variables, even though the cron already knows the platform and pack ID.
## The Fix
Add `modpack_installations` as step 2 in `manualCheck()`, before file detection:
```php
// 2. Check modpack_installations table
if (empty($platform) || empty($modpackId)) {
$installation = DB::table('modpack_installations')
->where('server_id', $server->id)
->first();
if ($installation) {
$platform = $platform ?: $installation->provider;
$modpackId = $modpackId ?: (string) $installation->modpack_id;
}
}
// 3. Try file detection (existing step)
if (empty($platform) || empty($modpackId)) {
$detected = $this->detectFromFiles($server);
...
}
```
Also — the widget only shows `latest_version` but not `current_version`. Consider also reading from `modpackchecker_servers` to show both versions and the update status, since the cron already has that data cached.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — modpack_installations ID Mismatch + finalized Issue
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Two Issues Found
### Issue 1: Only 5 of 50 rows have finalized=1
```
Finalized values: 1 0 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 ... (45 zeros)
```
Most installations are `finalized=0`. The query `->where('finalized', 1)` excludes them all.
**Fix:** Either remove the `finalized` filter, or change to `->where('finalized', '!=', null)` — or check what `finalized=0` means (in-progress install vs completed).
### Issue 2: server_id type mismatch
The join query (`modpack_installations.server_id = servers.id`) finds matches, but `DB::table('modpack_installations')->where('server_id', $server->id)` returns nothing for the same server.
Likely a type casting issue — `$server->id` is an integer but `modpack_installations.server_id` may be stored/cast as a string.
**Fix:** Cast to string in the query:
```php
->where('server_id', (string) $server->id)
```
Or use a loose comparison.
### Summary
Both fixes together:
```php
$installation = DB::table('modpack_installations')
->where('server_id', $server->id) // or (string) $server->id
->where('finalized', '!=', null) // or remove finalized filter entirely
->first();
```
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,26 @@
# Chronicler Dispatch — modpack_installations has no 'id' column
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Error
```
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'ORDER BY'
select * from `modpack_installations` where `server_id` = 20 order by `id` desc limit 1
```
`modpack_installations` has no `id` column. Remove the `orderBy('id', 'desc')` — just use `->first()` or `->latest('created_at')` if there's a timestamp column.
Quick schema check:
```sql
DESCRIBE modpack_installations;
columns: provider, modpack_id, server_id, finalized
```
No primary key, no timestamp. Just `->first()` is fine.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,55 @@
# Chronicler Dispatch — Use modpack_installations Table (Data Confirmed)
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## The Answer
The `modpack_installations` table already has everything we need. No Wings calls required.
```
Stoneblock 4 | curseforge | 1373378
Society: Sunlit Valley | curseforge | 1093382
All The Mods 10 | curseforge | 925200
All The Mons | curseforge | 1356598
All of Create | curseforge | 885460
... 19 servers total with CurseForge IDs
```
## Schema
```sql
modpack_installations:
server_id (FK to servers.id)
provider ('curseforge', 'ftb', etc.)
modpack_id (the pack ID)
finalized (1 = install complete)
```
## What CheckModpackUpdates.php Should Do
Replace the DaemonFileRepository approach entirely with a simple DB join:
```php
// Get all servers that have modpack data
$servers = Server::join('modpack_installations', 'servers.id', '=', 'modpack_installations.server_id')
->select('servers.*', 'modpack_installations.provider', 'modpack_installations.modpack_id')
->where('modpack_installations.finalized', 1)
->get();
```
Then use `provider` and `modpack_id` directly — no file detection needed for these servers. Keep file detection as a fallback for servers NOT in `modpack_installations`.
## Detection Priority Order
1. `modpack_installations` table (fastest, most reliable) ← ADD THIS
2. Egg variables (MODPACK_PLATFORM/MODPACK_ID) ← keep
3. DaemonFileRepository file scan ← keep as last resort fallback
This will immediately detect all 19 CurseForge servers on the live panel.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,24 @@
# Chronicler Message — Phase 11D Deployment Approach
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
Great work on 11D. File the deploy request when ready.
One important note for the request: **deploy to Dev Panel (64.50.188.128) first, NOT the live Panel VPS (45.94.168.138).**
Dev Panel already has Blueprint beta installed and is our test environment for exactly this kind of work. Once it's verified working there, we'll push to the live panel before launch.
Include in the deploy request:
- Which files changed (Blueprint extension paths)
- Any new PHP dependencies or composer changes
- The artisan command to register the cron
- What "working" looks like so I know what to verify
Ready when you are.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,23 @@
# Chronicler Dispatch — Dashboard Badge Not Rendering
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Badge still not showing — even in incognito
Build compiles, data exists in `modpackchecker_servers` for server `a49fc33c` (ATM9, update_available), but badge is not rendering on the dashboard.
## Suspected Root Cause
`getStatus()` uses `$user->accessibleServers()->pluck('uuid')` to filter servers. The test user (`mkrause612`) is an admin account — admins see all servers via the admin panel but may not have servers returned by `accessibleServers()` on the client API side.
**Please check:**
1. Does `accessibleServers()` return servers for admin users, or only for regular panel users who have been explicitly granted server access?
2. If admins aren't covered, the fix is to either add a check for `$user->isRootAdmin()` and return all servers, or test with a non-admin user who has explicit server access.
The DB data is correct — `a49fc33c | curseforge | update_available`. The API just may not be returning it for an admin user.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,39 @@
# Chronicler Dispatch — Add Badge Build Now
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Decision: Add it now
Michael wants to test on the live panel (which has additional Blueprint modules) as the real-world validation. If `yarn build:production` succeeds there, badges ship in v1.0.0 — not v1.1.0.
Go ahead and add the Node version detection + badge build to build.sh now. Use the exact implementation from the Gemini consult:
```bash
# Node version detection
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
# Fail fast on Node < 16
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
echo "Please upgrade Node.js on your panel and try again."
exit 1
fi
# Apply OpenSSL legacy provider for Node 17+
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
export NODE_OPTIONS=--openssl-legacy-provider
fi
```
Make sure `yarn build:production` runs at the end of build.sh after all injections.
Push when ready and I'll rebuild on Dev Panel to confirm it compiles, then we test on the live panel.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,52 @@
# Chronicler Dispatch — Dashboard Badges: v1.1.0 Implementation Plan
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Gemini Consultation Complete ✅
Full doc: `firefrost-operations-manual/docs/consultations/gemini-blueprint-css-build-2026-04-12.md`
## The Plan
**Path A wins.** Add smart Node version detection to build.sh. Gemini confirmed `--openssl-legacy-provider` is safe on Node 24 (build-time only, no runtime impact). Vanilla JS badges rejected as too brittle.
**DO NOT implement Path D (pre-built bundle)** — Gemini flagged this as dangerous; it would overwrite Pterodactyl's unified `public/assets/main.js` and break all other Blueprint extensions on the customer's panel.
## build.sh Changes for v1.1.0
Add this block near the top of build.sh, before any injection logic:
```bash
# Node version detection
NODE_MAJOR_VERSION=$(node -v | grep -oE '[0-9]+' | head -1)
echo "Detected Node.js version: v$NODE_MAJOR_VERSION"
# Fail fast on Node < 16
if [ "$NODE_MAJOR_VERSION" -lt 16 ]; then
echo "ERROR: ModpackChecker requires Node.js 16 or higher."
echo "Please upgrade Node.js on your panel and try again."
exit 1
fi
# Apply OpenSSL legacy provider for Node 17+ (fixes css-loader MD4 hashing)
if [ "$NODE_MAJOR_VERSION" -ge 17 ]; then
echo "Applying OpenSSL legacy provider for Node 17+ compatibility..."
export NODE_OPTIONS=--openssl-legacy-provider
fi
```
## BuiltByBit Listing Copy (v1.1.0 section)
Gemini's suggested wording:
> "v1.1.0 includes our new interactive Dashboard Badges! This feature requires a standard panel asset compilation. Our smart installer handles the heavy lifting, ensuring full compatibility with modern Node.js environments."
## Timeline
This is post-launch work — v1.1.0 milestone. No rush. Current v1.0.0 ships clean without badges.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,48 @@
# Chronicler Dispatch — Phase 11D Partial Deploy
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## What's Working
- Blueprint reinstalled successfully ✅
- Cron registered (`0 4 * * * php artisan mvc:validate`) ✅
- Admin page should be accessible ✅
## Blocker
`php artisan mvc:validate` fails with:
```
ERROR There are no commands defined in the "mvc" namespace.
```
**Root cause:** Blueprint's `requests.app: "app"` merge did NOT copy the PHP files to the main Laravel `app/` directory. Confirmed:
```
find /var/www/pterodactyl/app -name "LicenseService.php" -o -name "ValidateLicense.php"
→ (no output)
```
Files are still only in:
```
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Services/LicenseService.php
/var/www/pterodactyl/.blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php
```
## Question for Code
Is `requests.app` in `conf.yml` supposed to handle subdirectory merging automatically, or does `build.sh` need to explicitly copy `app/Services/` and `app/Console/Commands/`?
Workaround I can apply right now if needed:
```bash
cp .blueprint/extensions/modpackchecker/app/Services/LicenseService.php app/Services/
cp .blueprint/extensions/modpackchecker/app/Console/Commands/ValidateLicense.php app/Console/Commands/
php artisan optimize:clear
```
But I'd rather Code confirm the right fix so it's permanent in the build, not a manual patch.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,26 @@
# Chronicler Dispatch — Phase 11D Blueprint Controller Issue
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Root Cause Found
Blueprint auto-generates its own controller at:
```
app/Http/Controllers/Admin/Extensions/modpackchecker/modpackcheckerExtensionController.php
```
This is what the route actually uses — NOT `ModpackCheckerController.php` which Code wrote.
Blueprint's auto-generated controller didn't have `LicenseService`. I've patched it on Dev Panel by:
1. Adding `use Pterodactyl\Services\LicenseService;`
2. Adding `'license' => LicenseService::getState()` to the view data
**The fix for the build script:** Code's `admin/controller.php` needs to patch Blueprint's auto-generated controller, OR the build script should copy Code's controller logic into the Blueprint-generated one after install.
Refreshing the page now to verify the patch works. Will report back.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,44 @@
# Chronicler Dispatch — Callout Box Colors
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Callout Boxes Still Wrong
The bright cyan and orange fills don't match the Pterodactyl dark theme. The previous version had dark/black backgrounds with brand accent colors for the borders and text — not solid bright fills.
**What's needed:**
```css
/* Dark background, accent color border + heading */
.callout.callout-info {
background: #1a1a2e; /* dark background */
border-color: #00b4d8; /* cyan accent border */
color: #fff;
}
.callout.callout-info h4 {
color: #00b4d8; /* cyan heading */
}
.callout.callout-warning {
background: #1a1a2e;
border-color: #f59e0b; /* amber/orange accent border */
color: #fff;
}
.callout.callout-warning h4 {
color: #f59e0b;
}
```
Or use inline styles directly in the blade if that's easier. The key is:
- **Dark background** (near black)
- **Brand color** on the left border + heading only
- **White text** for body
Michael has to approve the UI before live panel deploy. This is the last visual blocker.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,36 @@
# Chronicler Dispatch — Order ID Case Sensitivity
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Phase 11D Working ✅
UI is live and functional:
- License section showing correctly
- "Not Activated" gray badge ✅
- PRO TIER locks on Check Interval + Discord Notifications ✅
## One Bug Found
Order ID lookup is case-sensitive. `test-001` fails, `TEST-001` works.
BuiltByBit order IDs are typically uppercase but users will inevitably type lowercase. Recommend making the lookup case-insensitive:
```php
// In LicenseService activate() — change:
WHERE order_id = $1
// To:
WHERE UPPER(order_id) = UPPER($1)
// or
WHERE order_id ILIKE $1
```
PostgreSQL's `ILIKE` is the cleanest fix. Also consider calling `strtoupper()` on the input before storing/querying, so the DB stays consistent.
Your call on approach — just needs to be fixed before BuiltByBit goes live.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,38 @@
# Chronicler Dispatch — Callout Text Contrast
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## UI Target Found ✅
Michael found the reference screenshot — the dark Blueprint theme with cyan info boxes and orange help box is correct and what we want.
## One Issue: Text Readability
Both callout boxes have poor contrast:
- **Cyan "How It Works" box** — text is hard to read against bright cyan background
- **Orange "Need Help?" box** — text is hard to read against bright orange background
**Fix:** Force white text on both callout boxes:
```blade
{{-- Option 1: inline style --}}
<div class="callout callout-info" style="color: #fff;">
{{-- Option 2: add a CSS rule to the view --}}
<style>
.callout.callout-info, .callout.callout-warning { color: #fff; }
.callout.callout-info a, .callout.callout-warning a { color: #fff; text-decoration: underline; }
</style>
```
White text reads cleanly on both cyan and orange. Links should also be white + underlined.
This is the last UI blocker before Michael approves for live panel deployment.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,17 @@
# Chronicler Dispatch — CSS Specificity Fix Needed
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
AdminLTE's callout background is overriding our custom styles. Applied `!important` as a local fix on Dev Panel:
```css
background: #1a1a2e !important;
```
Please add `!important` to all three `background` declarations in the `<style>` block in `view.blade.php` so the fix is permanent in the repo.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,23 @@
# Chronicler Dispatch — Phase 11D $license Variable Missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## 500 Error on Admin Page
After manually copying the updated `view.blade.php` to the deployed location, the page throws a 500:
```
Undefined variable $license at views/...index.blade.php:66
```
**Root cause:** The controller isn't passing `$license` to the view. The view references `$license['status']`, `$license['grace_expires']`, etc. but `controller.php` isn't populating it via `LicenseService::getState()`.
Please check `admin/controller.php` — does it call `LicenseService::getState()` and pass `$license` to the view? If not, that's the fix needed.
Once pushed I'll re-copy the controller and test again.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,19 @@
# Chronicler Dispatch — settings-notices partial missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
New 500 after the layout rewrite:
```
View [partials.admin.settings-notices] not found.
```
This partial doesn't exist in Blueprint beta-2026-01 on Dev Panel. Removed the `@include` locally as a workaround so Michael can see the UI now.
Please remove `@include('partials/admin.settings-notices')` from view.blade.php in the repo so the fix is permanent.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,33 @@
# Chronicler Dispatch — Phase 11D ModpackApiService Missing
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Syntax error fixed ✅. `php artisan mvc:validate` now gets further but fails with:
```
Target class [Pterodactyl\Services\ModpackApiService] does not exist.
Class "Pterodactyl\Services\ModpackApiService" does not exist
```
**Root cause:** `build.sh` copies `LicenseService.php` but not `ModpackApiService.php`.
```bash
find /var/www/pterodactyl/app -name "ModpackApiService.php"
(no output)
```
Please add this line to build.sh's PHP Classes section:
```bash
cp "$EXT_DIR/app/Services/ModpackApiService.php" app/Services/ModpackApiService.php
echo "✓ Copied ModpackApiService.php"
```
Push and I'll re-run immediately.
*— Chronicler #84, The Meridian*

View File

@@ -0,0 +1,25 @@
# Chronicler Dispatch — No Dashboard Badges
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## UI Test Results
- **Admin extension page** — pending callout color fix (being tested now)
- **Server page / console widget** — ✅ working as intended, test variables work
- **Dashboard server cards** — ❌ no badge dots visible
The orange/green update status badges are not appearing on the dashboard server cards. The `UpdateBadge.tsx` component was copied correctly by build.sh, and `ServerRow.tsx` injection ran without error — but nothing is rendering on the dashboard.
Please investigate:
1. Is `UpdateBadge` actually being injected into `ServerRow.tsx`?
2. Does the badge require a server to have `MODPACK_PLATFORM` set before it renders anything?
3. Is there a JS error in the browser console?
Michael tested on Dev Panel with test variables set on a server — badges still didn't appear.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

View File

@@ -0,0 +1,27 @@
# Chronicler Dispatch — Phase 11D Syntax Error
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
**To:** Code
---
## Progress
Build script fix is working — PHP files are now copying correctly to Laravel app/ tree. ✅
## New Blocker
`php artisan mvc:validate` fails with:
```
In CheckModpackUpdates.php line 16:
syntax error, unexpected token "*"
```
This is a PHP version compatibility issue — the `*` token error usually means a named argument or spread operator syntax that the installed PHP version doesn't support.
Dev Panel is running **PHP 8.3**. Please check line 16 of `CheckModpackUpdates.php` for anything that might not be compatible.
Once fixed, push and I'll re-run the build script manually and test again.
*— Chronicler #84, The Meridian*
**Fire + Frost + Foundation** 💙🔥❄️

Some files were not shown because too many files have changed in this diff Show More