Archive all completed REQs — bridge clear for Bitch Bot task

This commit is contained in:
Claude
2026-04-15 17:19:02 +00:00
parent f4f96dfe31
commit 364d6411ec
20 changed files with 0 additions and 0 deletions

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