Exact-match on 'corrosionmgmt.com' meant www. or any staging host
silently served the panel instead of the marketing site. Hosts now come
from VITE_MARKETING_HOSTS (comma-separated, defaults cover bare + www).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every page previously titled 'Corrosion Management' with zero meta -
marketing invisible to search and link previews. Router afterEach now
sets title/description/og per route (no new deps); marketing pages get
real content-backed descriptions, panel views mechanical titles.
index.html carries defaults for pre-JS crawlers. Verified in-browser
per page via Playwright.
test-runner.yml: per-tool presence checks instead of green-lighting
missing toolchains; workflow_dispatch instead of every push.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
discord.gg/corrosion is an Unknown Invite (verified via Discord API) -
no dead community promises on the marketing site. Re-add when a real
server exists. Discord *webhook* feature copy stays; that's shipped.
AGENTS.md Scout tier haiku -> sonnet[1m] confirmed by Commander:
marginal price difference, 1m context window pays for itself on recon.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-site fake-data audit findings:
- SetupWizard showed a curl|sh installer (get.corrosionmgmt.com) and a
'corrosion-agent' binary that don't exist -> real host-agent commands
- 'View live demo' CTA on 5 marketing pages linked to a login, not a
demo -> honest 'Sign in'
- Google Fonts @import was silently dropped from the production CSS
bundle (mid-bundle @import) -> <link> tags in index.html; prod was
shipping system fallback fonts
- App-root ErrorBoundary bricked the entire SPA (incl. marketing) on a
single failed fetch until manual reload -> resets on route change +
content-scoped boundary inside DashboardLayout so nav chrome survives
- Status page KPIs showed fake zeros while the fetch failed -> em dash
- Login lacked the forgot-password link (flow already existed end-to-end)
- AdminSeedService: fresh DB had schema but no login possible; seeds
super-admin + license from ADMIN_* env when users table is empty
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Version badge: was hardcoded '1.0.8' — now single-sourced from frontend/package.json (1.0.0) via Vite define __APP_VERSION__, so it auto-updates on release. Sidebar agent footer: removed the FABRICATED 'asgard-01' host name and the fake 'Agent v1.0.8' line — now shows real server.connection data, or an honest 'No host agent connected' empty state when nothing is deployed (the operator's actual state). Renamed 'Companion agent' -> 'Corrosion host agent' across the UI (ServerView/SetupWizard/Dashboard/Plugins), the binary names (corrosion-host-agent-<os>-<arch>) + CDN path (/host-agent/), the Go Makefile build output, and the Gitea CI workflow — frontend download links and CI output now match. Marketing hero mock host names neutralized (asgard-01 -> rust-host/dune-host/conan-host). DB column names (companion_last_seen) left intact. Build green; zero 'asgard'/'1.0.8' remain in frontend/src.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives the panel off the active game (GameSwitcher selection) + the GameProfile registry, so each game visibly differs (not just accent color). Sidebar nav: Rust = full (uMod plugins + plugin configs); Conan/Soulmask/Dune drop uMod + plugin-configs and relabel reset (Wipe World / World Reset / Deep Desert), Dune relabels Console->Broadcast (no RCON) and is Docker-managed. ServerView: management-model badge + game-appropriate panels (Rust deploy + Oxide; Dune Docker/BattleGroup-Sietches; Conan clans/thralls/avatars/purge; Soulmask main-client cluster) with HONEST EmptyStates where no backend data exists yet. Dashboard: per-game reset terminology + stat labels. No invented routes (all map to existing router entries); no fabricated data. Build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five marketing sub-pages built to match the landing's design language, all real content: Pricing (4 real tiers + Fleet Block + commercial-use definition + feature-comparison table + self-service support model), How it works (one agent -> N game instances, BYOS, no-SSH), FAQ (real support/product/games/billing Q&A reflecting the self-service model), Roadmap (honest Shipped/In-progress/Planned, no fake dates), Early access (real signup form). 3 icons added (circle/send/help-circle). Visually verified via Playwright; 0 console errors. Build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MarketingLayout + LandingView rebuilt from the delivered design as a multi-game platform site (was Rust-only stub): hero with per-game re-skin + panel mockup, 8-pain problem grid, agent-model shift, 4 self-themed game blueprints (Rust/Dune/Conan/Soulmask), core capabilities, wipe orchestration, built-like-infrastructure, public sites/storefront, pricing, serious-admins, final CTA, footer. REAL pricing (Hobby $9.99 / Community $19.99 / Operator $99.99 / Network $99.99 + $49.99 fleet block) + commercial-use definition + self-service support model ($125/hr prepaid blocks, 'a tool, not a managed service'). marketing.css ported (token-based). 6 icons added to the registry. No fabricated metrics/testimonials. Build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DashboardView now renders the REAL server from useServerStore (connection/config + live WebSocket stats) + real 24h history from /analytics/timeseries, with honest EmptyStates ('install the companion agent') when there is no data. DELETED _dashboardMock.ts (the fake 8-server fleet/feed/wipes). PlayersChart hardened: removed the DEFAULT_SERIES fallback, renders an 'awaiting telemetry' empty state instead of a fabricated curve. New gameProfiles.ts: real per-game capability/terminology/stat registry (rust/conan/soulmask/dune; dune managementModel=docker-compose), ready to wire when the backend gains a per-license game field. No fake data. Build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nested ./styles/corrosion.css barrel (8 @import token files) was placed after `@import "tailwindcss"`. Once Tailwind v4 expands in place, those nested @imports no longer precede all statements, so PostCSS DROPPED them (the 8 'should be written before any other statement' warnings). Result: every design token (--surface-*, --accent, --text-*, --font-brand, --space-*) was empty and the entire re-skin rendered unstyled (white bg, no surfaces/accent) despite a green build. Fix: import the 8 token files directly + contiguous in style.css. Verified live via Playwright — tokens resolve (--accent #f26622, canvas #0a0b0e) and the login renders correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auth (Login/Register/ForgotPassword/SetupWizard) + account cluster (Settings/Team/Notifications) re-skinned onto design-system components + tokens. JPEG login banner replaced with the C-gauge mark + Oxanium wordmark. All logic/store/router/handlers preserved (TOTP flow, validators, save handlers, API endpoints). Build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tokens ported 1:1 from the Claude Design bundle (colors/game-themes/type/spacing/elevation/motion/fonts) with the data-theme/data-game theming contract via useThemeGame (+ cc-skin-swap repaint guard). 23 design-system components reimplemented as Vue SFCs (core/forms/data/navigation/feedback/brand). DashboardLayout rebuilt as the game-aware shell (GameSwitcher, grouped nav with permission gating preserved, agent-health footer, topbar). DashboardView: Fleet + Solo with per-game GAME_FIELDS rows and the themed ECharts PlayersChart; Solo wired to the real server store, Fleet on representative data pending the multi-instance backend. All four game skins (Rust/Dune/Conan/Soulmask). vue-tsc + vite build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace individual plugin config sidebar entries with a single "Plugin Configs"
link that opens a card-based landing page. Cards show status (Active/Configured/
Not Configured), config count, and link to existing editor views. Search bar for
filtering. All existing plugin routes preserved for direct navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces flat 25-item navItems array with 6 labeled sections:
Dashboard, Server, Plugin Configs, Operations, Monitoring, Management.
Section headers only render when at least one item is visible to the
user's permissions. Platform Admin section restyled to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
KitsView: cast v-for Items array to fix string|number index type mismatch.
TimedExecuteView: remove unused X icon import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Migration 021: raidablebases_configs table with JSONB config_data
- Entity, module, controller (7 endpoints), service with NATS deploy/import
- Frontend: 4-tab editor (General, Difficulty, NPC, Loot & Rewards)
- Pinia store, types, router route, sidebar nav with Swords icon
- Top 30 most common settings with actual RaidableBases.json key paths
- Difficulty sub-tabs for Easy/Medium/Hard/Expert/Nightmare with spawn day toggles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DB migrations 017 (betterchat_configs) and 020 (timedexecute_configs) applied
- TypeORM entities matching production schema exactly
- NestJS modules with full CRUD + apply-to-server + import-from-server
- Pinia stores following teleport config pattern
- BetterChatView: Chat Groups editor with color pickers, font sizes, format strings; Settings tab with word filter, anti-flood, player tagging
- TimedExecuteView: TimerRepeat with presets, RealTime-Timer, OnConnect/OnDisconnect command lists
- Wired into app.module.ts, router, DashboardLayout nav
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /servers/install-oxide endpoint, NATS bridge for oxide.status,
server store installOxide method, ServerView Install Oxide card with
progress tracker matching the Deploy card pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the complete frontend for BetterLoot profile management:
- Pinia store (loot.ts) with CRUD, import/export, apply-to-server actions
- LootBuilderView orchestrator with profile bar, modals, two-column layout
- LootContainerSidebar with categorized container list, search, config indicators
- LootItemEditor for per-container item settings and ungrouped item table
- LootItemPicker modal with searchable/filterable Rust item grid
- LootGroupEditor for reusable loot group management
- Router integration at /loot-builder
- Sidebar nav item with Crosshair icon and loot.view permission
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prev/Next buttons at top and bottom of results table. New search
resets to page 1. Buttons disable at bounds and during loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The md:static approach wasn't reliably removing fixed positioning,
causing the sidebar to overlay the main content. Changed to keep
sidebar fixed (better for dashboards — no scroll) and offset main
content with md:pl-64 instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VueFinder requires app.use(VueFinderPlugin) to provide its internal
context (i18n, features, config stores). Without plugin registration,
the store returned null during setup, causing Object.keys to throw
TypeError on undefined.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Installs VueFinder and wires it to the backend /api/files endpoint with
JWT Bearer auth. Adds /files route, File Manager nav item (files.view
permission-gated, FolderOpen icon), and imports VueFinder CSS globally.
Driver token is computed reactively so it tracks token refreshes automatically.
Uses midnight theme to match the dark admin panel aesthetic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- GET /plugins/browse proxies uMod search.json filtered to Rust category,
with 5-minute in-memory Map cache to avoid hammering the upstream API
- POST /plugins/upload accepts .cs files up to 5 MB via multipart, persists
to plugin_registry, and dispatches plugin_upload action over NATS so the
companion agent can write the file to the game server
- Legacy GET /plugins/search stub preserved (now directs callers to /browse)
- FileInterceptor + @UploadedFile follow the existing maps upload pattern
Frontend:
- useApi composable gains upload() method for multipart/form-data requests
(omits Content-Type so the browser sets the correct multipart boundary)
- plugins store adds browseUmod() calling GET /plugins/browse and
uploadPlugin() calling POST /plugins/upload with FormData;
UmodPlugin and UmodBrowseResult TypeScript interfaces exported
- PluginsView Browse tab now calls browseUmod() through the backend proxy
(no cross-origin requests to uMod directly); results show title,
downloads_shortened, and latest_release_version_formatted from the
real uMod payload
- New Upload Custom tab: drag-and-drop or click file input for .cs files,
client-side extension/size validation, spinner during upload, success
toast + auto-switch to Installed tab on completion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ServerView: automation toggles (crash recovery, auto-update, force wipe eligible)
now call updateConfig() on click with toast success/error; all catch blocks get
toast feedback instead of silent swallow
- PluginsView: Browse uMod tab wired to /plugins/search backend endpoint with
debounced search, results table, and Install button that calls installPlugin();
shows install state and marks already-installed plugins
- WipesView: dry-run results now displayed in a collapsible panel (would_delete /
would_preserve lists + estimated duration); schedule enable/disable toggle wired
to PUT /wipes/schedules/:id with loading state and toast feedback
- AnalyticsView: catch blocks now show toast errors instead of console.log;
Player Retention placeholder replaced with intentional placeholder cards
- TeamView, ChatLogView, PlayersView, NotificationsView, MapsView: all empty
catch blocks and '// API not wired yet' comments replaced with toast.error()
calls; Notifications save now shows success toast
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NATS has no auth configured, so NATS_TOKEN was a placeholder that
confused users. Made it optional in the Go agent (default empty) and
removed it from Quick Setup commands. Also removed GAME_SERVER_PATH
since one-click deploy handles server installation. License key already
auto-populates from auth store after previous commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- admin.service.ts: createLicense() now uses CORR-XXXX-XXXX-XXXX format
instead of raw hex hash
- admin.service.ts: getLicenses() flattens owner_email in response to
match frontend expected shape
- auth.service.ts: Login/register responses now include full license
object so frontend can populate auth store
- auth.service.ts: Email lookups are case-insensitive (LOWER()) to
prevent duplicate accounts from case variations
- LoginView/RegisterView: Call setLicense() after setAuth()
- AdminLicenses: Handle null expires_at (was showing Dec 31, 1969),
fix nullable types, fix query param name (per_page → limit)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Go deployment orchestrator with platform-specific SteamCMD install,
Rust server download, server.cfg generation, and service registration.
Wire deploy command subscription in daemon, make GameServerPath optional,
add InstallDir config with OS-aware defaults. Fix unused imports and
WebSocket subscribe API in ServerView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds DeploymentConfig/DeploymentStatus types, deployment state management
in the server store, tabbed Linux/Windows quick setup commands, and a
Deploy Rust Server card with progress tracker and configuration form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DashboardView: Add non-null assertion on upcoming[0] (guarded by length check)
- EarlyAccessView: Add missing `computed` import from Vue
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI pipeline now uploads binaries to cdn.corrosionmgmt.com:
- /companion/latest/ (always current, overwritten each release)
- /companion/v1.x.x/ (versioned archive)
Frontend download links updated from Gitea releases to CDN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend:
- Add Companion Agent card to ServerView (status, download links, setup instructions)
- Shows agent connection status, last heartbeat, license key for copy
- Download buttons for Linux/Windows amd64 from Gitea releases
CI/CD:
- Fix build-companion.yml: replace actions/github-script with Gitea API curl
- Inject version from git tag via ldflags (-X main.version)
- Add VERSION variable to Makefile with ldflags injection
- Change main.go version from const to var for ldflags compatibility
Deployment:
- Add systemd service file (deployment/corrosion-companion.service)
- Add .gitignore for bin/ (binaries should come from CI, not repo)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: super_admin JWT returned early with no license_id, causing
@CurrentTenant() to pass undefined to every tenant-scoped service query.
- jwt.strategy: Move license lookup before super_admin early return so
admins who own licenses get their license_id in the JWT payload
- CurrentTenant decorator: Throw 401 with clear message when license_id
is undefined instead of letting undefined cascade into TypeORM queries
- Wipe store: Fix 6 wrong routes (/profiles → /wipes/profiles, etc.)
and remove redundant manual license_id guards
- Changelog module: Add stub controller/service returning empty array
to eliminate 404 on /api/changelog
- ChangelogView: Handle both array and {entries} response shapes
- AGENTS.md: Streamlined 3-tier roster (Opus/Sonnet/Haiku)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `new Date(x).toLocaleString()` pattern with `safeDate()` utility to prevent crashes on null/undefined timestamps in scheduled task next_run field.
Part of ongoing safe formatter migration across all Vue views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
API returns partial/empty stats object — individual fields can be undefined
even when the object exists. Added null guard to prevent toLocaleString crash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auth service used flat env var names (JWT_SECRET, JWT_ACCESS_EXPIRY_SECONDS)
but @nestjs/config nests them under jwt.* — configService.get() returned
undefined, so expiresIn was 0 and tokens expired on issue (iat === exp)
- JWT strategy had same bug for secretOrKey
- AnalyticsView passed /api/analytics/... to useApi which already prepends /api,
resulting in /api/api/analytics/... (404)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>