Patch Notes
Recent entries stay expanded, older dates collapse by default, and long update lists can be expanded per section. This keeps the latest work readable without losing the full project history.
Bug Fixes
- SRP — final broken-state cleanup: reopening the form editor no longer stacks duplicate click handlers that can send the same add, delete, or save request multiple times, and add-question failures are now visible. Directors can deny a permanently unavailable pending killmail instead of leaving the claim stuck forever. History views also distinguish temporarily unavailable killmail details from a genuinely empty claim history.
- SRP — follow-up broken-path review: restored the missing question-type selector so saving the form no longer converts dropdown and multi-select questions into text boxes. Question deletion now removes its option controls, empty forms save safely, and editor actions display success or failure. A killmail that ESI cannot load now remains visible as a retryable pending placeholder without blocking every other director claim. Itemless killmails no longer throw, nested container contents are included in reimbursement valuation, and recent-loss duplicate filtering is scoped to the pilot's current corporation while still permitting previously denied claims to be resubmitted. The SRP history migration rollback now recreates minimal deleted-question records before restoring its required foreign key, preserving historical answers.
- SRP — security, duplicate-processing, and history correctness hardening: all SRP write actions now require antiforgery validation; claim submission, approval, denial, and payout completion serialize competing requests so double-clicks or parallel requests cannot create duplicate active claims or payouts. Approval now validates the reimbursement percentage and the exact ESI insurance level/amount instead of trusting browser values, including preserving a legitimate 0% reimbursement. Dropdown and multi-select answers are checked against the corporation's configured choices. Saved answers retain their original question text when forms are edited or questions are deleted, paid and payment-pending statuses now reflect actual payout records, unavailable ESI history entries produce a visible warning instead of silently disappearing, and name/insurance lookups are batched to reduce ESI traffic. Director errors now display the server's specific reason.
- SRP — confirming a claim still failed inside the database write: pending claims now initialize legacy insurance and denial text fields instead of relying on nullable production columns. The transactional save also identifies whether the claim row, form answers, or commit failed, and returns the innermost database message rather than Entity Framework’s generic “An error occurred while saving” wrapper.
Bug Fixes
- SRP — zKillboard links could still fail after “Fetch Killmail” began responding: zKill imports previously depended on the app server reaching zKillboard to discover the protected ESI hash, which can fail under production network or Cloudflare policy. The resolver now checks the signed-in pilot’s ESI kill history for the matching hash first and uses zKillboard only as a fallback. The fetch toast also reads the server’s actual
{ error }response, so duplicate, non-victim, invalid-link, and upstream API failures no longer collapse into the generic “Could not fetch that killmail.” message. - SRP — missing character killmail scope produced fetch errors instead of requesting access: SRP now checks for the exact
esi-killmails.read_killmails.v1grant before loading the page or calling either killmail endpoint. Characters missing it are sent through EVE SSO with their existing grants preserved, then returned to SRP after authorization. Already-open SRP tabs also recognize the structured missing-scope response and start the same reauthorization flow instead of displaying an ESI failure. - SRP — completing the reimbursement form ended with “Submission failed”: the browser posted one JSON object, but
SubmitSRPdeclared three separate action parameters without[FromBody], so ASP.NET Core received empty killmail values and rejected every claim—including corporations with no SRP form. Submission now uses a single body-bound request model, scopes answer collection to the open dialog, supports a genuinely empty form, and displays the server’s real error. Claim and answer writes are also transactional, preventing partially saved claims. Manually fetched killmail URLs are retained for the browser session and their cards are restored after navigating away or refreshing, then cleared once submitted, so pilots do not need to paste and fill the same loss repeatedly. - SRP — a committed claim could still report “Failed to submit SRP”: the post-save Discord-server lookup was still inside the main submission failure handler, allowing an optional notification/read failure to turn a successful database commit into an error response. Notification work is now fully isolated from claim persistence, and genuine validation or save exceptions return their specific stage and underlying error instead of the unhelpful generic message.
Bug Fixes
- SRP — “Fetch Killmail” button did nothing: the pasted-URL control had no click handler or server endpoint. It now accepts ESI killmail and zKillboard kill URLs, resolves zKillboard hashes through the configured API client, verifies the signed-in pilot is the loss victim, and inserts the normal SRP submission card. Invalid, duplicate, inaccessible, and non-victim killmails now show a useful error, and pressing Enter in the URL field also fetches the loss.
Bug Fixes
- Buyback order form — pasted inventory rejected because of the "*" EVE adds to item names: copying a stack out of the in-game inventory produces lines like
Biomass* 2001, and the order parser used that name exactly as pasted, so it never matched a real item and the whole order failed to resolve. Item names are now cleaned before lookup — asterisks stripped and non-breaking / duplicated whitespace collapsed — through a new sharedItemNameSanitizerhelper used by the buyback order form and public buyback page (ParseHelper), the logistics paste parser (ParseItemsProcess), the shared item name resolver (ItemNameResolverService), and the Setup → Buyback price-rule item box (AddPriceRule). You can now paste straight from the client without editing the text first.
Bug Fixes
- Recruitment → New Member Guide Setup — steps would not save and the editor closed on click: the Save / Add / Delete Step AJAX calls sent the antiforgery token only as the
__RequestVerificationTokenHTTP header, which nginx strips (headers containing underscores), so[ValidateAntiForgeryToken]rejected them with a 400. The token is now also sent in the POST body. The editor also used to slide shut the instant "Save Step" was clicked — before the save even ran — so it now closes only after a successful save, keeping your content on screen if the save fails. - Recruitment → Welcome Page Setup — "Failed to save. Please try later": same cause —
SaveWelcomePagerelied on the underscore header alone. The token is now sent in the POST body and the page saves. - Recruitment → Recruitment Page — "An unexpected server error occurred." with emoji / web icons: the
recruitmentstable (andwelcomepages,newmemberguideentries) was still on the legacyutf8mb3charset, which cannot store 4-byte characters, so MySQL threw "Incorrect string value" and the save 500'd. Startup now converts these tables toutf8mb4once (guarded by an information_schema check), so content containing emoji/icons saves correctly. - Recruitment → Form — questions could not be deleted: the delete handler read the question id from the button's immediate parent, which carries no id, so the server never got a valid
_QuestionIdand nothing was deleted (reloading didn't help because nothing was removed). It now reads the id from the enclosing.Questionelement and removes both the question row and its answer-options block. - Setup → Discord Bot — Corp Title → Discord role mappings would not save and "Submit" did nothing: the save posted a JSON body of shape
{ model: [...] }, butSaveTitlehad no[FromBody], so ASP.NET Core never bound the list and nothing was persisted. The action now uses[FromBody], the client posts the array directly with numeric ids, and Submit shows a success/error toast. - Setup → Discord Bot — deleting a Corp Title left the row on screen until reload: the delete succeeded server-side but the DOM cleanup targeted the wrong element (
.parent('.TitleRow')instead of.closest('.TitleRow')). The row is now removed immediately on delete. - Antiforgery tokens dropped by the reverse proxy — many AJAX buttons silently failed with 400: the app sent the antiforgery token as an HTTP header named
__RequestVerificationToken, but nginx strips request headers containing underscores by default, so every AJAX/fetchPOST that relied on the header (rather than a form-body token) was rejected. The header was renamed toRequestVerificationToken(no underscores) on both the server (AddAntiforgeryHeaderName) and the client token senders (EHR.js, Industry pages, Alliance/Setup wizard helpers), so it now survives the proxy. This restores a broad set of previously-broken actions, notably all Industry ESI sync / connect / action buttons (Landing, Command, Delivery, Hub, Workbench), Setup → User Management (approve / delete user), and CorpStore → Store Config item toggles / EFT parse. Body form-field tokens were unaffected. - Recruitment → New Member Guide — drag-reordering steps didn't persist:
SaveStepOrderreceived the order as a JSON body but lacked[FromBody], so it never bound and the change was discarded; it also relied on the underscore header for its token. It now binds the body (and posts numeric ids) with the token on the renamed header, so reordering saves.
Bug Fixes
- Alliance setup — "Create Alliance" failed with a security-token error: the Create Alliance button sent the antiforgery token only as the
__RequestVerificationTokenHTTP header. nginx (the reverse proxy in front of the app) strips request headers containing underscores by default, so the header was dropped before it reached the app and[ValidateAntiForgeryToken]rejected the request with a 400 (surfaced as "Your security token has expired"). The request now also sends the token in the POST body — matching how the rest of the app passes antiforgery tokens — so creating an alliance from the setup page works again. - Buyback & mining op confirmation — "ESI returned null." error: validating orders against ESI contracts uses
GetContracts,GetCorpContracts, andGetCorpContractItemsinAPIHelper. When any of those hit a real ESI error (expired token, 403 missing role/scope, 404, rate limit, timeout) the methods swallowed the exception and returnednull, so the wrapper reported the generic "ESI returned null." and the contract-validation classifier had nothing to match — you just saw the opaque message. These three methods now read the HTTP status code and ESI response body and throw a descriptive error, and the expired-token path now surfaces "Authentication expired…" instead ofnull. Buyback validation now shows the real cause and correctly prompts for re-authorization / missing scopes.
Bug Fixes
- Milestone events could be counted twice when retried: event recording now checks the source type, source key, and user before inserting, while retaining the database unique index as protection against concurrent writers. This also keeps deduplication consistent in non-relational test environments.
- Corp goal automated test contained contradictory completion assertions: removed the incorrect assertion so the test now matches its target values and explanatory comments.
Security
- SharpCompress dependency upgraded: updated SharpCompress from 0.39.0 to 0.50.0, removing the known moderate-severity advisory reported by the release build.
Bug Fixes
- Logistics order submit — intermittent "ESI returned null." failure: submitting an order containing an item not yet cached locally (e.g. certain blueprints) triggered a live ESI
/universe/types/{id}lookup through the sharedWebcall<T>helper, which — unlike every authenticated ESI call inAPIHelper— sent noUser-Agentheader. ESI throttles/rejects requests without a User-Agent, which surfaced as the opaque "ESI returned null."Webcallnow sends the same User-Agent (plusHost,Accept, and a 30s timeout) as the rest of the app, andLogisticsOrderWorkflowServiceretries the item fetch once and names the failing item in the error message. - Alliance Setup — "Create" button always showed a generic error: the
CreateAllianceclick handler inSetup.cshtmlonly displayed the server's real error message when the response contained a JSON error body. Empty-body HTTP failures (a400from an expired anti-forgery token, or a403from the Setup permission filter) caused the message parse to fail, falling back to a hardcoded "Failed to create alliance." that hid the actual cause. The handler now surfaces a status-specific message — expired security token (400), not authorized / session expired (401/403), connection failure, or the raw HTTP status — while still preferring the server's error message when present.
Changes
- Industry — AJAX conversion extended to Command, Delivery, and Landing views: all remaining single-action form POST buttons in
Command.cshtml(SetEsiConnection, SyncEsiAssets, SyncEsiBlueprints, SyncEsiJobs, SyncEsiWallet, SyncEsiMarketOrders, LinkEsiJob, CreateOrderFromEsiJob, IgnoreEsiJob, IgnoreEsiJobType),Delivery.cshtml(MarkPaidFromDelivery, DeliverFromDelivery), andLanding.cshtml(SetEsiConnection, SetMemberEsiConnection) now usefetch()AJAX instead of form POSTs. AddedCommandErrorhelper toIndustryControllerand extendedIsIndustryAjaxdetection to all affected controller actions. Connect/set-connection actions reload the page after success; sync and row-action buttons update in place.
Changes
- Industry — action buttons converted from form POSTs to AJAX: clicking Approve, Deliver, Re-Check, Start, Complete, Ready, Cancel, Refund, Recover, Reassign, and ESI Sync buttons in the Hub and Landing views no longer causes a full-page refresh. Buttons now fire
fetch()calls with anX-Industry-Ajaxheader; on success the card or row fades out in place with a CSS transition. AddedIndustryActionhelper andIsIndustryAjaxdetection toIndustryController— all endpoints remain backwards-compatible with non-JS form submission.
Changes
- Build Queue — Stock Request modal item field replaced with blueprint dropdown: the free-text "Item Name" search input (which required managers to type any EVE item name and confirm a Type ID) has been replaced with a
<select>dropdown populated from the corp's active blueprint profiles. This restricts stock requests to items the corp actually has blueprints for, and removes the separate "Type ID" confirmation step. If no blueprints have been configured yet, the modal shows a prompt to add them via the Workbench page.
New Features
- Mining — configurable invoice payment wallet division: corps can now select which of their 7 wallet divisions is monitored for invoice payments. Previously the
InvoicePaymentMatchServicewas hardcoded to scan division 1, meaning payments made to any other division were never auto-matched. The new setting appears as a "Invoice Payment Wallet Division" dropdown (1–7) in the Setup Wizard system settings step and defaults to division 1 for all existing corps.
Bug Fixes
- Corp Store — cart "+" quantity button did nothing after first item added:
renderCart()looked up#cartEmptyviadocument.getElementByIdon every call, but the first render with items in the cart rancontainer.innerHTML = ''which removed the element from the DOM. Subsequent lookups returnednull, causing aTypeErroronnull.style.displaybefore the cart could update — so pressing "+" appeared to do nothing. Fixed by capturing the element reference once in the IIFE closure at page load.
Bug Fixes
- SRP — pending SRPs disappeared during ESI name resolution outages:
GetKills()calledLookupApi.TryGetNamesto resolve character/corp names and usedcontinueon failure, silently skipping the entire killmail. Any ESI hiccup caused all pending SRP entries to vanish from the review page. Fixed to log the failure and proceed with an empty names dictionary so killmails always appear (names render blank rather than the entry being hidden). - Mining Ops — "Please select a Pricing Calculator" error blocked all corporate ops: the JS validation in the op creation form checked whether the pricing select had a value, but the select element is only rendered when public pricing types exist in the database. When no pricing types were configured,
$('.mc-pricing-select.PricingType')returned an empty jQuery set,.val()returnedundefined, and the validation error fired for every op regardless of type. Fixed by adding a.length > 0guard so the check only runs when the element is actually present. - Mining — automatic invoice payment matching never ran:
FindPayments(the endpoint that matchesplayer_donationjournal entries with#Invoice:reason codes to unpaid invoices) was a manually-triggered HTTP GET and was never called by any background process, so corp wallet payments were never auto-matched regardless of whether the invoice ID and amount were correct. Added a newInvoicePaymentMatchServicebackground service that runs hourly: it finds all corps with unpaid invoices, fetches division-1 journal entries using the corp director's ESI token, and marks any matching invoice paid automatically.
Bug Fixes
- Industry Queue — accepted Store orders never appeared in Build Queue:
QuickSaveBlueprintProfile(used by the Workbench quick-add and ESI bulk import) was storing the blueprint item's TypeId (e.g. "Brutix Blueprint" = 709) asProductTypeIdinstead of the manufactured product's TypeId (e.g. "Brutix" = 638).ExecuteAcceptOrdermatches queue blueprints by product TypeId, so the lookup always missed and orders fell through to the manual LogiOrder path. Fixed to useproductItem.type_id(the resolved product). Existing profiles added via quick-add must be archived and re-added to pick up the corrected TypeId. - Industry —
AddDiscordOpenAuthmigration not applied at startup: the migration was missing[DbContext(typeof(EHRContext))], so the startupGetPendingMigrations()loop never found it andEnableOpenAuth/OpenAuthRolewere never added to the database, causing a runtime crash when loading any page that queriesDiscordServers. Added the missing attribute. - Build —
DiscordControllermissingusing Discord.WebSocket;:SocketGuildUsercould not be resolved, causing a compile error that prevented the project from building. Added the missing namespace import.
New Features
- Industry — Blueprints & Queue workbench redesign: the legacy Industry Workbench has been replaced with a focused two-tab page at
/Industry/Workbench. The Blueprints tab lists blueprint profiles as cards — each showing ME/TE/kind and a one-click Queue on/off toggle — alongside an Import from EVE panel that shows blueprints found in the corp's in-game inventory (via ESI sync) so managers can bulk-select and add multiple profiles at once. The Build Config tab shows a read-only summary of the cost profile rates, structure profiles, queue pricing mode, and ESI connection status, with direct edit links. A newBulkAddBlueprintProfilesaction handles the import submission, reusing the existingQuickSaveBlueprintProfileservice method which automatically resolves "Brutix Blueprint" → "Brutix" and fetches materials from Fuzzwork.
Changes
ToggleBlueprintProfileQueueandDeleteBlueprintProfilenow redirect back to the newWorkbenchpage instead of the legacyIndexaction.
Bug Fixes
- Recruitment — assets viewer missing wormhole / in-space assets: characters with ships floating in space (any solar system — especially common in wormhole corps with no NPC stations) had those assets silently omitted from the ESI asset viewer. Added a third In Space section that groups all
location_type: solar_systemassets by solar system name. WH system names (e.g. "J123456") are resolved via the same ESI names endpoint used for stations. Both the full_Assetslazy-load panel and the_AssetsMiniinline view now show this section.
New Features
- Discord — Open Authentication: corps can now enable open auth on their Discord server via a new toggle in the Discord setup page. When enabled, any EVE player who runs
/authbut fails the normal corp/alliance membership check is no longer rejected — they receive a verified nickname formatted as[THEIR_TICKER] CharacterNameusing their actual corporation ticker pulled from ESI, and can optionally be assigned a configurable open auth role. Corp and alliance members continue through the full HR auth flow unchanged. Both the toggle and the optional role assignment are managed from the new "Open Authentication" section of the Discord setup page.
New Features
- Logistics — corp JF route approvals: managers with
LogisticsSetuppermission can now designate one saved JF route per destination as the corp-approved canonical route via a new panel at the bottom of the JF Planner. Approving a route automatically removes approval from any previous route for the same destination. Two new endpoints back this:GetCorpJfRoutes(lists all corp routes with pilot names) andSetJfRouteApproval.
Bug Fixes
- Logistics — build error in
AdvanceTemplateSchedule: the monthly schedule branch used a C# immediately-invoked lambda ((() => { })())) which is not valid in switch expressions and producedCS0149 Method name expected. Replaced with a privateAdvanceMonthlyhelper method. - Logistics — hauler stats widget always showed zeros:
GetMyHaulerStatsreturned PascalCase JSON properties (CompletedThisMonth,TotalVolumeM3, etc.) while the JS read camelCase. All values rendered as zero. Fixed to return camelCase property names. - Logistics — JF saved route list blank and delete non-functional:
GetMyJfRoutesused an EF projection with PascalCase member names; the JS readr.savedJfRouteId,r.originName, etc. — allundefinedunder the global PascalCase JSON policy. Rows rendered blank and delete sentid=undefined. Fixed by using explicit camelCase names in the projection. - Logistics — approved JF route could be silently deleted: a hauler could delete their own route even if it was corp-approved and actively pricing all orders to that destination.
DeleteJfRoutenow returns an error if the route is approved, directing the hauler to ask a logistics manager to unapprove it first. - Logistics —
SetJfRouteApprovalconcurrent-approval race and false success fixed: two managers approving different routes for the same destination in parallel could both commitIsApproved = true, and a route deleted between the existence check and the UPDATE would still return "Route approved." Replaced with a single atomicUPDATE … SET IsApproved = (SavedJfRouteId = id) WHERE … DestinationId = ?that clears and sets in one statement, then verifies the target was actually set. Also removed a row-count check that incorrectly returned "Route not found" for no-op approvals on already-approved/unapproved routes. - Logistics —
DeleteJfRouteTOCTOU race could silently delete an approved route: a hauler could delete a route that was concurrently approved by a manager — the approval ran between the existence/approval check and the EFRemovecall, leaving the destination with no approved route. Replaced with an atomicExecuteSqlRawDELETE filtered toIsApproved = 0; if no row is deleted the endpoint now returns a specific error indicating the route became approved concurrently.
Changes
- Logistics — JF pricing now requires an approved route:
FindJfPlanandGetJfRouteForOrder(JF mode) now only considerIsApprovedroutes. If no approved route exists for a destination, order submission is blocked with a message directing a logistics admin to approve one via the JF Planner. Previously any hauler's most-recently saved route could drive corp-wide order prices. - Logistics — gate-mode JF opt-in uses corp-approved route: the opt-in toggle now only appears when an approved route exists for the order's destination, and the fuel charge is based on that approved route. Previously any corp member's saved route for the same leg could be used.
Bug Fixes
- Logistics — active-runs chip showed wrong reward: the hauler view's "My Runs" banner displayed the corp margin (
TaxAmount) instead of the actual haul reward (RequiredReward). Fixed to show the correct value. - Logistics — reward popup had no data in requester view: the reward calculation popup reads
data-rewardfrom order list items, but all three order status blocks only emitteddata-reqreward, leaving the popup empty. Added the missingdata-rewardattribute to all three blocks. - Logistics — gate-mode JF fuel used pilot isotope rate:
ApplyJfOptIncomputed fuel cost using the pilot's saved plan isotope rate rather than the corp's reference rate, making fuel charges inconsistent between pilots. BothApplyJfOptInand theGetJfRouteForOrdergate branch now useCalculateJfFuelCostwithReferenceIsotopesPerLy. - Logistics —
SyncEsiContractStatusmethod mismatch: the action was decorated[HttpGet]but the JS sent a POST request, causing routing to silently reject every sync attempt. Changed to[HttpPost]and updated the client-side call. - Logistics — double-completion race on
CompleteLogisticsOrder: two concurrent requests could both pass the status check and both fire milestone events and Discord notifications. Replaced the EF load-and-set with an atomicExecuteSqlRawUPDATE filtered toLogisticsOrderStatus = 1so only the first request succeeds. - Logistics — multi-accept race on order accept: concurrent haulers could load the same pending order before either saved, accepting it twice. Both
AcceptLogisticsOrdersandAcceptMultipleLogisticsOrdersnow useExecuteSqlRawwithLogisticsOrderStatus = 0in the WHERE clause. - Logistics — ESI route fetched on every item edit: updating item prices on an existing order re-fetched the gate route from ESI even though the route hadn't changed. Added an update-path guard that reconstructs route price from stored jump counts, eliminating the unnecessary ESI call.
- Logistics — scheduled template placed for deleted user:
ProcessScheduledTemplateOrderscontinued placing orders even if the template creator had left the corp and their account no longer existed. Added a null check that logs an error and skips the template. - Logistics —
CancelLogisticsOrdersIDOR: orders were loaded by ID and corp only, with ownership checked after load — any authenticated corp member could transiently touch another member's order. The initial DB query now includes the ownership condition. - Logistics — cancellation hard-deleted orders: cancelling an order permanently removed the row and all its items, losing audit history. Added
Cancelled = 4toLogisticsOrderStatusand changed cancellation to a soft-delete (status update only). - Logistics — monthly schedule recomputed
AddMonthsthree times:ComputeNextScheduledUtc's monthly branch calledcandidate.AddMonths(1)three separate times in a one-liner, each call potentially evaluating at a different instant. Refactored to store the result once in a local variable. - Logistics — monthly template schedule drifted day-of-month:
AdvanceTemplateScheduleusedcurrent.AddMonths(1)which collapses a 31st to the 28th and keeps it there on subsequent cycles. Replaced with the same day-clamping logic asComputeNextScheduledUtc. - Logistics — extra DB call in
LogisticsOrdersaction: a localint UserIddeclaration shadowed the base-classUserIdproperty, triggering a redundantGetUserIdquery on every requester page load. Removed the local declaration. - Logistics —
LogisticsOrderStartaccessible without login: the action was missing[Auth], allowing unauthenticated access to the new-order start page. Added the attribute. - Logistics —
fmtAgeoff-by-one for local timezones: the age calculation anchored dates toT12:00:00Z(UTC noon), producing wrong "Today / Yesterday" labels for users west of UTC in the morning. Replaced with a local-midnight comparison (T00:00:00). - Logistics —
fmtIskinconsistent between views: the hauler view appended " ISK" to formatted amounts ("1.23B ISK") while the requester view used bare suffixes ("1.23B"). Unified to bare suffixes. - Logistics — dead exception handler around
String.Split:LogisticsOrderWorkflowServicewrappeditems.Split('\n')in a try/catch that could never trigger on a non-null string. Removed the dead handler. - Logistics — misleading action name
UpdateVolumePricingCategory: the action only returns a refreshed dropdown partial and updates nothing. Renamed toGetVolumePricingDropdownand updated all three JS call sites in_LogisticsVolumePricing.cshtml. - Logistics —
SaveJfRouteopened two database contexts: one context was opened for the JF-mode permission check and a second for saving the route. Merged into a single context.
New Features
- ESI token health check: new page at
/Account/EsiHealthCheck(also linked from User Management → Account Settings) that checks every character linked to the logged-in account. For each character it attempts an ESI token refresh and verifies theesi-characters.read_corporation_roles.v1scope is present. Failures appear with a Fix button that sends only that character through EVE SSO re-auth and returns to the health check page. The list auto-updates after each fix so resolved characters disappear without a manual refresh. Useful for diagnosing why an alt's mining ledger sync is failing without waiting for an error toast. - Logistics — ESI sync fires milestones and Discord on completion: when
SyncEsiContractStatustransitions an order from In Progress → Completed it now fires the same milestone events (orders completed, volume hauled) and Discord delivery notification as the in-appCompleteLogisticsOrderaction. Haulers who rely on ESI sync to close their runs no longer miss loyalty points or Discord pings.
Bug Fixes
- Logistics — order ownership check on update:
UpdateOrderwas checkingShipperId == userIdinstead ofOwnerId == userId. Requesters couldn't update their own pending orders; haulers who accepted an order could. Fixed toOwnerId. - Logistics — JF fuel re-priced silently on item update: updating item prices on a pending order re-ran
ApplyJfOptInagainst the pilot's newest saved plan, silently changingRequiredRewardafter the orderer had agreed to a price. Item-price updates now preserve the existing JF fuel cost. - Logistics — hauler could delete a Completed order:
CancelLogisticsOrdersnow returns an error for any Completed order regardless of who calls it, preventing a hauler from erasing a completed-but-not-yet-received run. - Logistics — earnings this month showed wrong value:
EarningsThisMonthon the hauler view summedTaxAmount(corp margin) instead ofRequiredReward(actual haul reward). In RoutePlusVolume mode this showed entirely wrong earnings figures. - Logistics — "Use JF" toggle never appeared for non-haulers:
GetJfRouteForOrderwas scoped to the current user's saved plans, so requesters who aren't also JF pilots never saw the toggle even when a corp hauler had a matching route. Now searches all corp plans for the origin/destination pair. - Logistics — ISK/m³ efficiency sort used wrong field: available-orders efficiency ranking used
TaxAmount / Volumeinstead ofRequiredReward / Volume, producing incorrect sort order in RoutePlusVolume mode. - Logistics —
GetLogisticsPermissionsrejected POST requests: the action had no[HttpPost]attribute but the JS called it via POST; routing silently rejected every call. Added[HttpPost]. - Logistics — JF ship preset used wrong isotope rate: the quick-fill "JF" preset in the hauler ship panel set 1,000 iso/ly — approximately 3.5× too low for all four jump freighter hulls at JDC 0. Corrected to 3,500 iso/ly.
- Mining op — payout stall on unknown ore type:
PayoutProcess.Oreswas a static dictionary initialized once and never refreshed. If CCP added a new ore type after app startup, any sync containing that ore would callFail()and leave the op stuck on "Ledger refresh completed, but payout recalculation stalled." The dictionary is now backed by a 1-hourMemoryCacheTTL so new types are recognised within the hour without an app restart. - Mining op — ESI ledger timeout too short: the per-character ESI mining-ledger request timeout was 5 seconds (direct-fetch path) and 10 seconds (background cache), causing syncs to fail under normal CCP server load and pushing a continuous stream of error toasts. Both timeouts raised to 30 seconds. Configurable via
AppSettings:MiningOpLedgerRequestTimeoutSecondsandMiningLedgerCache:FetchTimeoutSeconds. - Mining op — public op pricing calculator always rejected: creating a public mining op showed "Please select a Pricing Calculator" regardless of which calculator was chosen. The form has two
PricingTypeIdinputs — a hidden one (for serialization) and the visible dropdown — and validation read the hidden one which is always empty. Fixed by scoping validation to the visible dropdown and adding achangehandler that syncs the visible selection into the hidden field so form serialization also sends the correct value.
Bug Fixes
- Scope-required authorize button: hardened the shared ESI scope modal redirect so clicking
Authorize ScopeorReauthorizetrims the returned auth URL, closes the dialog, and navigates withwindow.location.assign(...)on the next tick. This fixes logistics order scope prompts where the button could appear to do nothing after a solar-system lookup requested refreshed ESI access. - Logistics scope prompt false positive: logistics order creation no longer falls back to authenticated ESI search for item-name or solar-system resolution. Item names and missing systems now resolve through public
universe/idslookups and local caches, so users who already have the required login scopes are not bounced into a misleadingScope Requiredprompt for normal Amarr/Jita-style orders. - Public ESI search helper scope fix:
APIHelper.Search(...)now resolves inventory types and solar systems through publicuniverse/idsinstead of requiringesi-search.search_structures.v1for every lookup. Logistics submit also reports leaked search-resolution failures as normal order errors instead of opening the scope modal. - Logistics submit scope-modal kill switch: removed the logistics submit client-side calls that converted
ScopeRequiredJSON into the shared auth modal. If any stale server path still returns a scope-shaped response, the order form now shows a normal submit error instead of sending users into the dead-end authorization prompt. - Static script cache busting: local layout script references now use
asp-append-version, includingDashboard.jsandLogistics.js, so deployed JavaScript fixes are fetched by browsers instead of being hidden behind stale cached files.
New Features
- Solar system DB seeding: added EF migration
20260613000002_SolarSystemSdeDatathat inserts all 8,036 navigable EVE solar systems (k-space + wormhole) from the Fuzzwork SDE into theSolarSystemstable with security status and starmap coordinates. Migration first deduplicates any rows sharing the samesystem_id, adds a unique index onsystem_id, then inserts all missing systems. No runtime SDE download is required on the production server — the SQL is embedded directly in the migration. - Solar system cache pre-warm:
SolarSystemCacheis now warmed at startup immediately after DB initialization, so the first autocomplete request against the solar system list is served from memory rather than triggering a blocking full-table load.
New Features
- Discord verification button: directors can run
/postverifyin any channel to post a persistent "Verify with Eve-HR" button. Members click the button and receive an ephemeral auth link visible only to them — no DM required. The original!authcommand is retained as a fallback. /authslash command: replaces!authas the primary auth entry point. Responds ephemerally with a personal auth link instead of sending a DM./refreshrolesslash command: re-applies a member's corp or alliance roles and updates their server nickname on demand, without requiring a full re-auth through the website./milestonesslash command: shows the calling member's earned milestone badges and active milestone progress bars, including current vs. target values and LP reward info. Ephemeral./goalsslash command: shows all active corp goals with progress bars and window type. Public response to drive corp engagement./rankslash command: shows the calling member's rank and contribution percentage on each active corp goal. Ephemeral./leaderboard <goal>slash command: shows the top 10 contributors for a named corp goal with medal emojis and alt counts. Public./price <item>slash command: replaces the three separate!SellPrice,!BuyPrice, and!LogiPricecommands with a single combined embed showing sell, buy, and logistics prices together.- Slash command migration: all existing
!prefix commands now have/slash command equivalents —/help,/wallet,/skillpoints,/mail,/loyaltypoints,/ops,/topbounties,/who,/fuel,/apply,/reminder,/currentreminders,/zkillstatus,/updateusers. All original!commands are retained.
Changes
- EVE SSO login progress handoff: the EVE SSO callback now validates the callback, creates a short-lived single-use login ticket, and immediately shows a signing-in progress page while the existing token exchange, character verification, scope checks, user/corp setup, and sign-in work completes through a follow-up request. Existing
SaveChanges()boundaries remain intact so database-generated IDs are still created before dependent login steps use them. - EVE SSO login progress replay fix: the progress completion request now reuses the already-validated OAuth state payload from the pending login ticket instead of validating the one-time state nonce a second time, preventing the progress page from flashing and then failing with "Login expired."
- Topbar staged loading: the logged-in header now loads user/corp, payout, events, loyalty points, milestones, and PI status through the fast nav endpoint, while wallet, unread mail, skill points, and skill queue each show their own spinner and load through independent cached requests so one slow ESI call no longer blocks the whole topbar.
- Topbar placeholder cache fix: wallet, mail, skill, and queue chips no longer cache fallback placeholder values such as
--, and stale placeholders are cleared before retrying so the individual chip spinners remain visible while fresh ESI data is requested. - Topbar ESI metric sequencing: wallet, mail, skill points, and skill queue now request their ESI-backed values one at a time after showing spinners, avoiding parallel token/API contention that could leave the chips stuck on fallback values.
- Topbar ESI batch loading: the slow header chips now load through one sequential server-side ESI request after the page renders, matching the old working nav behavior more closely while keeping the immediate topbar spinners.
- Topbar ESI cache window: wallet, unread mail, skill points, and skill queue now use ESI-only values with a shared 10-minute server cache, including a fresh cache key version so stale placeholder values from earlier attempts are ignored.
- Topbar ESI source correction: payout remains DB-backed, while wallet, mail, skills, skill queue, and planet status now load from ESI through the 10-minute topbar metrics cache. The metrics response includes per-field ESI error details for debugging without showing those details in the header.
Bug Fixes
- Logistics order item-name lookup cache: logistics item parsing now resolves pasted item names from a singleton in-memory cache warmed from the local
Itemstable at startup before calling ESI. If an item is missing locally, Eve-HR resolves it through public ESI IDs first, stores the resolved item inItems, and updates the cache immediately so later orders do not depend on each submitter's ESI search token. - Logistics multi-buy quantity parsing: pasted logistics rows now parse comma-formatted quantities such as
10,000correctly and normalize pasted whitespace before lookup, fixing space-aligned EVE multi-buy exports that were failing or reading only the digits before the comma. - Logistics duplicate item rows: logistics order submission now groups local
Itemsrows bytype_idbefore building lookup dictionaries, preventingAn item with the same key has already been addedcrashes when the database contains duplicate item records such as type ID17476. - Logistics solar-system autocomplete: origin/destination autocomplete now searches cached solar-system names by contains as well as prefix, so typing a middle fragment such as
115can surface matching systems from the localSolarSystemscache. - Refining ore reference data: added an EF migration generated from the June 10, 2026 Fuzzwork SDE comparison against
Dump20260602.sql. It widensOres.Volumeprecision, inserts missing ore rows, inserts their mineral-yield mappings, corrects stale compressed-ore volumes, and updates Scordite/Mordunium pyerite yields without requiring production to download SDE at startup.
Bug Fixes
- Logistics route planner — solar systems not found: systems not yet encountered in any logistics order (e.g. null-sec destinations) were invisible to the route planner. Typing a valid system name in the origin or destination field returned no autocomplete results and the route calculation silently returned empty. Both the autocomplete and route logic now fall back to the ESI
universe/idsendpoint on a miss, resolve and cache the system, then proceed normally. - Route planner — null-dereference on no ESI search results:
APIHelper.Search()called.FirstOrDefault()directly on the deserializedsolar_systemandinventory_typelists without null-guarding them. If ESI returned no matches, the list was null and threw an exception. Changed to?.FirstOrDefault() ?? 0. CharacterListmissingsolar_systemsfield: the ESIPOST /v1/universe/ids/response includes asolar_systemsarray, butCharacterListonly mappedcharacters, so solar system ID resolution viaTryGetIdsalways returned null. Field added.
New Features
- In-memory solar system cache: new singleton
SolarSystemCacheservice that lazy-loads the fullSolarSystemstable from DB on first request, then serves all subsequent name-prefix searches and ID lookups from in-memory dictionaries. On cache miss it resolves the system from ESI, adds it to the cache immediately, and backfills the DB asynchronously in the background.
Changes
- Logistics route planner (
SearchSolarSystemsandGetRouteDanger) now useSolarSystemCacheinstead of issuing per-request DB queries. Route-building no longer opens a DB context or callsSaveChangesinline — new systems discovered during route resolution are written to the DB in a background task.
Bug Fixes
- Mining op alt reauth — "Op is no longer active" error: clicking Re-authorize alt from the mining op notification panel (or the Refresh ESI link in the alt member row) would show "Op is no longer active or Access link has ended" after completing the EVE SSO flow. This happened because
MiningOpAdAltdid not include the current op code in the OAuth state, so if the user authorized as their main character at EVE SSO instead of switching to the alt, the callback fell into the invite-code join path with an empty code and failed.MiningOpAdAltnow looks up the user's active op and carries its code in the state. As a backstop, theMiningOpScopecallback now redirects back to the mining ops page instead of erroring when the code is empty. - Buyback orders appearing in Mining Op Confirmations: buyback orders were showing up in the Mining Op Confirmations queue alongside regular mining ops.
BuildMiningOpConfirmationQueueViewModel,ConfirmAllOp, andValidateAllOrdersnow filter out buyback orders so the mining op queue only shows mining operations.
New Features
- Buyback Confirmations page: new dedicated page showing only buyback orders pending confirmation. Includes per-order Validate, Confirm, Cancel, and Block actions, plus Confirm All and Verify All bulk buttons. Accessible from the sidebar under the BuyBack section.
Bug Fixes
- Logistics pending order row layout: pending orders in the My Freight Orders list now use the same flex row alignment as in-transit and delivered orders, with a pending-specific row class and compact age/date metadata so the icon, route, and status no longer stack into a taller inconsistent layout.
- VS Code debug rebuild visibility: the debug prelaunch
kill-apptask now stops the EVE HR app host and VS Code debug adapter instead of relying on a stale fixed PID, so rebuilt Razor views are not hidden behind an old running debug host.
Bug Fixes
- Hauler stats — active runs always 0:
GetMyHaulerStatsreturned the active order count under the keyCurrentActivewhile the JS stats widget expectedactiveRuns, so the count always displayed 0. Property renamed to match. - ESI sync — status change silently ignored:
SyncEsiContractStatusreturned the updated order state asorderStatusbut the client handler readdata.newStatus, so order cards never updated their status chip after a sync. Fixed the JS handler to use the correct property name.
New Features
- Logistics admin dashboard: a new Logistics → Dashboard page (director/setup permission) shows live corp-wide stats — pending and in-progress counts with total volumes, completed and expired order counts for the last 30 days, average delivery time, contract mismatch count, pending order aging buckets (< 24 h / 24–48 h / 48–72 h / > 72 h), and a top-5 haulers leaderboard ranked by volume.
- ESI in-game contract helper: the order detail panel for pending orders includes an "In-Game Contract" card showing required volume, collateral, and reward. Members can enter a contract ID to link it; a Sync Status button auto-advances the order to In Progress or Completed when the contract is picked up or delivered in-game. A mismatch warning badge appears if the contract's reward or collateral is outside 1% tolerance.
- Order notes panel: each order detail panel now has a live-loaded notes section where the requester and the assigned hauler can exchange notes. Hauler notes appear in amber; member notes in blue.
- Hauler trip bundling: the available orders view now groups pending orders by destination. Each group header shows combined volume, estimated reward, and an "Accept All" button that accepts every order in the group at once. A capacity warning appears if the group total would exceed the hauler's largest registered ship.
- Hauler stats widget: the available orders view shows a "My Stats" bar with deliveries this month, volume hauled, active runs, and average delivery time.
- Pending order aging indicators: orders pending more than 24 hours show an amber left-border accent in the order list; orders pending more than 72 hours show red.
New Features
- Logistics order templates: corp members can now save any order as a named, re-orderable template. Templates appear in a collapsible sidebar on the Order Wizard page, grouped into CORP and PERSONAL sections with a live search bar. Clicking Order Now on a template opens a review dialog showing the route and items, then places the order in one click.
- Corp doctrine templates: users with the new Logistics Template Manager permission can publish corp-wide templates visible to all corp members — ideal for doctrine fits or recurring supply runs. Personal templates remain available to every member without additional permissions.
- Save as Template from order form: a Save as Template button in the order form footer lets members name and save any in-progress order as a template. Corp Template Managers also see a toggle to mark it as a corp-wide template.
- Save as Template from order history: a Save as Template button in the order detail footer of the My Freight Orders view lets members re-package any past or active order without re-entering items.
- Logistics Template Manager permission: new
LogisticsTemplateManagerpermission (value 36) grants users the ability to create and delete corp-wide templates. Assigned from Logistics → Permissions alongside the existing Hauler permission. - Logistics Permissions & Order Templates nav items: Permissions is now listed in the LogisticsSetup sidebar and Order Templates is added to the Logistics section so both pages are reachable without knowing the direct URL.
- Logistics Orders redesign — three-column layout: the My Freight Orders page is now a unified three-column view: templates on the left, the order list in the middle, and an adaptive detail panel on the right that switches between template detail and order detail depending on what is selected. Templates and orders are now managed from one screen without navigating away.
- Template scheduling: logistics order templates can now be configured with a Weekly or Monthly auto-order schedule. Choose a day of the week or day of the month, enable the schedule, and the background processor automatically places the order at the next scheduled time — no manual intervention needed. The next fire time is shown in the template detail panel.
Bug Fixes
- Logistics route danger duplicate systems:
GetRouteDangernow tolerates duplicateSolarSystems.system_idrows when building the route lookup dictionary, preventing duplicate-key crashes on route systems such as30002381. - Logistics cancel null crash:
CancelLogisticsOrdersaccessedorder.ShipperIdandorder.OwnerIdbefore checking whether the order was found, crashing with a null reference when an order ID didn't exist. The null guard now runs first. - Logistics order update accumulating dropped items: updating a logistics order only added or updated items from the new paste without removing lines that were no longer present, so removed items could accumulate indefinitely on the order. Stale items are now deleted before the upsert pass.
- Logistics complete action typo: the
CompletedtLogisticsOrdersaction had a straytin its route name. Renamed toCompleteLogisticsOrder; client JS and hauler view updated to match. - Logistics cancel duplicate Discord notification:
CancelLogisticsOrdershad anif/elsethat calledLogisticsOrderCanceledwith identical arguments on both branches. Collapsed to a single call. - Logistics mutations accepting GET requests: 14 state-mutating logistics actions (accept, complete, cancel, receive, place order, update order, save settings, set permissions, manage ships, manage volume pricing) were missing
[HttpPost]and accepted GET requests. All are now POST-only.
New Features
- zkill route danger strip: when a hauler selects a pending contract, a "Route Danger" section loads asynchronously in the contract detail panel. Each system on the route is shown as a chip with a colour-coded security status badge and a kill count badge (grey → green → yellow → orange → red) sourced from zkillboard, summed over the last two calendar months. zkill responses are cached in-memory for one hour per system so the same system is never fetched twice within a cache window.
Changes
- ESI lookup caching:
LookupApiClientnow caches ESI item lookups (5 min), solar system lookups (30 min), and route results (6 hr) inIMemoryCache, so repeated lookups for the same type ID, system ID, or route within a cache window skip the outbound ESI call entirely. - Logistics read-only query optimisation: added
.AsNoTracking()to all display-only EF queries inLogisticsController— order lists, available orders, hauler ships, permissions, and volume pricing — reducing per-request memory allocation from EF change tracking. - SolarSystems name index: startup schema repair now creates an index on
SolarSystems.name, eliminating full-table scans on system name lookups during order placement and route danger checks.
Fixes
- Logistics order submit feedback: logistics order creation now validates origin, destination, pasted items, user account, and corp logistics settings before saving, and the order form now displays validation/server errors instead of only spinning and silently stopping.
- Logistics order form placeholders: the origin, destination, delivery recipient, and item text fields now use example placeholders instead of pre-filled sentinel values, making it clearer that locations are typed solar-system names rather than selected default values.
- Recruitment page save always failing: saving the recruitment page content and URL always returned a "failed to save" error because the AJAX POST did not include the anti-forgery token required by
[ValidateAntiForgeryToken]. The page now renders a hidden token and sends it with the save request. - SDE sync crashing with a concurrency error: the SDE ore/mineral sync failed with a
DbUpdateConcurrencyExceptionbecauseBackfillRefiningMappingsCoreshared the main EF context and called its ownSaveChanges()mid-sync while tracked ore entities were still live. It now runs in its own isolated context. - Logistics order "ESI returned null" on unrecognised item names: submitting an order with an item name that ESI's search couldn't match showed the opaque error "ESI returned null." instead of identifying the bad item.
ParseItemsProcessnow treats a search result of0(no match) as a failure and surfaces "Invalid Item Name: …" so users know exactly which item to fix.
Fixes
- AdSense slot sizing and ad-block prompt detection: ad containers now reserve visible space before AdSense fills them instead of starting at
max-height: 0, preventing hidden slots from blocking ad fill. The ad-block prompt now tracks whether the real AdSense script loaded instead of treating the local queue placeholder as a successful load, so blocked AdSense requests can show the support prompt for non-patron users. - Industry ESI ignore schema repair: startup schema repair now ensures the ESI job ignore columns exist (
industryesiconnections.IgnoredProductTypeIdsandindustryesijobentries.IsIgnored) so databases with migration-history drift no longer crash the Industry Hub with unknown-column errors. - Industry ESI wallet/market schema repair: startup schema repair now also ensures the wallet journal and market order sync status columns, backing tables, and indexes exist so partially migrated databases do not fail Industry Hub loads on missing ESI sync fields.
- Industry notification settings table repair: restored the runtime table mapping for
IndustryNotificationSettingstoindustrynotifsettingsand added startup repair for the table/index so Industry Hub loads do not fail when notification settings are missing. - Industry build template schema repair: startup schema repair now ensures
industrybuildtemplates,industrybuildtemplateitems, and their indexes exist so partially migrated databases no longer crash when the Industry Hub loads template summaries.
Changes
- Industry ESI-confirmed production start: preparing a build order no longer marks it as running in EVE-HR. Queued orders now move to an "Awaiting ESI start" state and wait for corp industry job sync or manual ESI link confirmation before moving to In Progress.
- Industry job reconciliation and slot accuracy: ESI sync now auto-matches unambiguous prepared orders by product and run count, links the ESI job, and counts only ESI-confirmed running jobs as occupying in-game production slots.
Fixes
- Discord setup emoji-name sync: Discord role, channel, and server names containing emoji or other 4-byte Unicode characters are now normalized before saving setup metadata.
- Corp wallet journal header tolerance: corporation wallet journal reads no longer throw when ESI omits pagination or rate-limit headers; missing headers now fall back safely.
Changes
- Discord setup failure diagnostics: setup refresh failures now log the full exception string plus pending Discord row diagnostics so charset-related save failures can be traced to the exact row.
New Features
- Wallet journal sync with payment auto-confirmation: added a Sync Wallet button to the ESI panel on the Command view. Fetching the corp wallet journal (division 1) via ESI, the sync stores new
player_donationentries in a newIndustryWalletEntrytable and automatically matches incoming ISK transfers to unpaid build orders by amount — first trying to narrow by the paying character's ID, then falling back to amount-only when a unique match exists. Matched orders are instantly marked as Paid with aPaymentReceivedAtUtctimestamp and an audit log entry. The ESI panel shows total entries synced and highlights when auto-matching is active. - Corp market order sync: added a Sync Orders button to the ESI panel. It fetches all active corp market orders from ESI and stores them in a new
IndustryEsiMarketOrdertable (replaces the full set on each sync). The Command view ESI panel shows the total active buy order count. The Procurement Hub's "Buy from Market" section now shows a Market Order column for each shortage material — a green "Active · N% filled" badge appears when a matching corp buy order is live in EVE, with a tooltip showing volume remaining and price per unit. Materials without an active buy order show "No buy order" in grey.
Bug Fixes
- Bulletin editor anti-forgery fallback: the Edit Bulletins page now generates a page-local anti-forgery token and includes it in both AJAX headers and POST bodies for all bulletin operations (list, load, create, save, delete), preventing 400 errors when header-only token forwarding is stripped or unavailable.
New Features
- Corp Mining Op share payouts: added an optional share-based payout path for Corp Mining Ops. Directors enable share payout on the start-operation form (locked after launch), manage participant shares and adjustments during the op via the live view, and finalize payout on the confirmation page. Share modes: per-main, per-character, or manual. Includes an optional corp share cut, sync-time accrual, and startup schema repair so existing databases don't crash before migration.
- Template bulk-order creation: the Run Template page now shows a ⚡ Create All Orders button when two or more products have a valid plan. Clicking confirms the product list then calls
CreateOrdersFromTemplate, which creates one build order per template item using independent EF contexts so each order correctly sees prior material reservations. Items that fail (e.g. no preferred structure configured) are skipped with a per-item error shown as a flash on the Hub page. - Reaction ISK/week column: the Reaction Profit Tool table now shows an ISK/wk column alongside ISK/hr, showing projected profit for one reaction slot running continuously for 7 days (
IskPerHour × 168). A matching sort button is in the toolbar.
New Features
- ESI job ignore controls: untracked ESI jobs in the Order/Job Reconciliation panel now have two new action buttons. Ignore hides a specific job from the reconciliation list by setting
IsIgnoredon itsIndustryEsiJobEntryrow. Always Ignore permanently suppresses all future ESI jobs of that product type by storing the type ID in a newIgnoredProductTypeIdsfield on the ESI connection — useful for jobs that are always run outside the app and should never clutter the reconciliation view. Both actions are new controller endpoints (IgnoreEsiJob,IgnoreEsiJobType) backed by a migration. - Structure slot timeline: the Command view now has a Structure Slots panel showing every configured manufacturing and reaction structure as a card. Each card displays numbered slot rows filled with active or queued build jobs — product name, run count, ETA, a Running/Queued status badge, and a link to the dependency tree. Remaining slots are labeled Free, and structures with no active jobs are also shown so idle capacity is immediately visible. Slot counts come from each structure profile's configured
JobSlotCount. - Operational queue (action feed): the Command view now shows an Operational Queue panel above the setup health check whenever any items need attention. Each item is color-coded by severity: green for wins (queued orders with payment cleared, orders ready for delivery), red for blockers (orders blocked, shortages awaiting a procurement decision), amber for warnings (running jobs past estimated finish, blueprint library/ESI conflicts), and blue for idle capacity (reaction slots with free space). When no items are present the panel is hidden.
- Campaign planner: added
/Industry/Campaign— a production planning page where you select a blueprint profile and quantity, then see the full bill of materials broken down by supply action: Inventory (already in stock), Manufacture (blueprint profile exists to build in-house), React (in-house reaction formula available), or Buy (market purchase required). The plan also shows total cost, estimated market spend, build duration, and the current slot utilization at the target structure. A "Create Build Order" link takes you directly to the workbench with the same inputs pre-selected. The planner is accessible from the Command view top bar and the sidebar nav. - Priority-based reservation conflicts: shortages in the Shortages & Procurement panel now detect when a lower-priority build order is holding a reservation on the same material a higher-priority order needs. A "Priority conflict" badge and conflicting order details appear inline on the shortage row. A one-click ↑ Reassign button strips the reservation from the lower-priority order (blocking it), then re-runs approval on the higher-priority order so it can immediately claim the freed material — resolving priority inversion without manual intervention.
- Discord notifications: the Command view now has a Discord Notifications setup panel where directors paste a webhook URL and toggle which events post to Discord — order created, approved, blocked, started, production complete, ready for delivery, delivered, payment received, materials shortage, and job failed. Each event sends a color-coded embed to the configured channel. Dispatched fire-and-forget so a slow or unreachable webhook never blocks the main workflow.
- Production analytics: added
/Industry/Analytics— a reporting dashboard showing 30-day throughput (orders delivered, ISK shipped, margin, material cost spent), average fulfillment time from creation to delivery (90-day sample), active pipeline broken down by status with total pipeline value, slot utilization per structure color-coded by fill percentage, shortage health by resolution type, top 10 products by order count over 90 days, and the 10 most recent deliveries. Linked from the Command view top bar and the sidebar nav. - Procurement hub: added
/Industry/Procurement— aggregates all outstanding material shortages across every active order into a single shopping list, grouped by resolution type: Unassigned, Buy from Market, React In-House, and Manufacture In-House. Each material shows the total quantity needed across all orders, estimated ISK cost (from manual price data), and the relevant formula or blueprint profile name. Expanding a material shows which orders need it with priority badges and dependency tree links. A "Copy list" button exports the list as tab-separated text for pasting into a Jita buyer. Linked from the Command view top bar and sidebar nav. - Production schedule page: added
/Industry/Schedule— a job queue planning view showing all Approved and Queued build orders grouped by their assigned structure, sorted by priority (Urgent first) then creation date. Each structure shows currently active/queued jobs with status dots, ETAs, and dependency tree links, then the waiting queue with projected start times based on current slot occupancy. Orders with no open shortages are marked "✓ ready"; those with unresolved shortages show a shortage count pill. A hero strip shows total ready-to-start count, total queued orders, and structure count. Orders without a structure assignment appear in a separate Unrouted section. Linked from the Command view top bar and sidebar nav. - Campaign planner optimization modes: the Campaign Planner now offers two supply strategies via radio button before computing the plan. In-House First (default) resolves non-inventory materials through manufacture and reaction blueprints first, falling back to market only when no in-house option exists. Market First skips manufacture/react entirely and buys all non-inventory materials — fastest path, highest ISK spend. A callout banner compares the two: in In-House First mode it shows the ISK saved vs a full market buy; in Market First mode it shows the extra cost vs in-house production and what the alternative market spend would have been.
- Build templates: added
/Industry/Templates— a library of named production templates where each template holds one or more blueprint profiles with target quantities. Clicking Run on a template opens/Industry/RunTemplate/{id}, which aggregates the full bill of materials across all template products (combining duplicate materials), shows per-product cost chips and a "→ Campaign Planner" link for each, and lets you toggle supply strategy (In-House First / Market First) before creating individual build orders. Templates can be renamed, annotated, and have their item lists edited inline with add/remove row controls. - Critical path panel on dependency tree: the Order Tree page now shows a Critical Path panel below the header. It traces the longest dependency chain end-to-end across all linked child orders (e.g. React Ferrogel → Manufacture Components → Build Ship), with left-to-right step cards showing each order's ETA, a clickable link to that child's own tree, and an "Earliest completion" countdown at the top. If any step has no ETA yet the panel shows a note that chain timing is incomplete. Computed via a recursive walk of the full active-order family tree using the new
ComputeCriticalPathmethod. - Delivery dashboard: added
/Industry/Delivery— a payment tracking and bulk-delivery page. A KPI strip shows ready-to-deliver count, awaiting-payment count, and total value. Each ready order shows product, customer, delivery location, quoted price, payment status badge (Paid / Unpaid / Waived + received timestamp), priority, and how long it has been ready. Inline Mark Paid and Deliver buttons handle individual orders with confirmation dialogs; an unpaid-order warning banner appears when any orders are not yet paid. Checkbox-based bulk deliver lets you deliver multiple orders in one action — with a separate warning if any selected orders are unpaid. A Recent Deliveries card below shows the last 30 days of delivered orders as an audit trail. Linked from the Command view top bar and sidebar nav. - Order history & profitability: added
/Industry/History— a per-order profit reconciliation view. A 6-stat KPI strip shows total orders delivered, revenue, material+job cost, gross profit, average margin %, and average fulfillment time. Date filter pill tabs let you scope the report to 30/90/180/365 days or all time. The sortable table shows each delivered order's product, quantity, customer, cost, revenue, gross profit, margin %, payment status, and fulfillment days — profit and margin are colored green or red. Linked from the Command view top bar and sidebar nav. - BPC staleness warnings: the Command view now shows a BPC Staleness panel when any active orders use BPC blueprints with run-count problems. Two warning types are surfaced: Insufficient runs (library run count is lower than the order's required runs — critical, shown in red) and Runs changed in ESI (ESI-synced run count differs from the library record — warning, shown in amber). Each entry links to the affected order's dependency tree for quick investigation.
- Multi-campaign planner: added
/Industry/MultiCampaign— an extended planner where you select multiple blueprint profiles with quantities in a single form (with JS-powered add/remove rows), then compute an aggregate BOM across all products at once. Results show per-product cost chips, individual "→ Campaign planner" and "+ Order" links, and the combined bill of materials table with supply actions, quantities, and estimated costs — the same output as Run Template but without needing to save a named template first. Linked from the Command view top bar and sidebar nav.
Fixes
- Discord corp auth list null render: initialized alliance auth rows in the Discord corp auth view model and made the corp/alliance auth partial tolerate missing lists, preventing the setup page from throwing when no alliance auth rows are loaded.
- ESI token refresh backoff: stopped repeatedly retrying users with revoked or five-failure ESI refresh tokens, added
NextApiCheckbackoff after failed refresh attempts, and cleared that backoff on successful token refreshes so stale tokens no longer flood ESI or the error log. - Reaction tool interactions and theme: enabled the Reactions page source controls, rewired filters/sorting/row expansion through DOM-ready handlers, and blended the page styling into the existing Industry blue/cyan theme.
- Industry workbench routing: added an explicit
/Industry/Workbenchroute for the full edit screen and updated Hub/Reactions links so Workbench and build-quote actions no longer fall back to the wrong landing page.
New Features
- Corp mining op history: replaced the CSV-only mining-op nav entry with a Corp Mining Op History page that lists confirmed corp ops, opens detailed per-op member/item breakdowns, and exports each op directly to CSV.
- Reaction profit tool page: added a dedicated
/Industry/Reactionspage backed by live reaction opportunities with search/filter/sort controls, expandable material/fee/profit breakdowns, and a sidebar navigation entry. - Industry quote audit panel: build quote previews now include an expandable "How this was calculated" section showing material cost, job fees, hauling estimate, margin applied, member discount, and effective profit margin — so the full cost breakdown is visible before creating an order.
- Industry inventory freshness badges: each manual inventory row now shows a ✓ Fresh or ⚠ Stale indicator based on the cost profile's staleness threshold, helping planners quickly identify outdated stock figures.
- Industry inventory allocation breakdown: the Reserved column in the Manual Inventory table now expands to show which orders are holding each material, grouped by order status (approved, blocked, queued, in-progress, child builds) with order counts per group.
- Industry setup health checklist: expanded the Command view's setup health panel into a 9-check guided checklist with colored dot indicators covering cost profile, manufacturing/reaction structures, pricing formula, blueprint profiles, owned blueprints, inventory freshness, reaction formulas, and market hub. Panel auto-opens when any checks fail.
- Industry Command inventory source labels: inventory rows in the Command view now show human-readable scope labels (Manual stock, Metenox drill, Incoming contract, Buyback buffer) alongside per-row staleness indicators.
- ESI asset sync foundation: added an ESI Asset Sync panel to the Command view — corp directors can designate their character as the authorized ESI connection, run on-demand corp asset syncs (
SyncEsiAssets), and immediately see connection status, last sync time, and how many ESI types are in inventory. Sync results are stored asIndustryInventoryMaterialrows withScopeName = "esi"and kept current on each sync. - ESI blueprint sync: added
IndustryEsiBlueprintEntryentity and migration plus aSyncEsiBlueprintsaction that fetches all corp blueprints from ESI, resolves type names from the item database, and auto-matches each entry to the owned blueprint library by product name. The Command view ESI panel now has a dedicated Blueprints row showing sync status, total blueprint count, and how many matched an existing library entry — synced independently from assets via its own button. - ESI industry job sync: added
IndustryEsiJobEntryentity and migration plus aSyncEsiJobsaction that fetches all corp industry jobs from ESI (including completed), maps activity IDs to readable names, resolves product and blueprint type names from the item database, and resolves installer character names from the user table. The Command view ESI panel now has an Industry Jobs row showing sync status and active/ready/total job counts. - ESI inventory reconciliation: the Command view now shows an Inventory Reconciliation panel below the main grid whenever ESI asset sync has run. The panel compares ESI corp asset quantities against manual/planned inventory totals for each tracked material type, highlights discrepancies with a likely cause (consumed outside app, added outside app, or ESI-only item), and shows a green badge when all quantities agree.
- Order/job reconciliation: the Command view now shows an Order/Job Reconciliation panel when ESI job sync has run. The panel surfaces untracked ESI manufacturing jobs (active jobs with no linked app order) and app orders missing an active ESI job. Where product types match, it suggests a link with High or Medium confidence and provides a one-click Link button to associate the ESI job with the build order via
LinkEsiJob. - Build order dependency tree: added
/Industry/OrderTree/{id}— a dedicated page for any active build order showing the full bill of materials as a dependency tree. Each material row shows source type (manufacture, reaction, market, or inventory), quantity needed vs available vs unreserved, a readiness badge, ETA or linked child-order number, and estimated material cost. Production Queue rows in the Command view now have a tree icon linking directly to that order's dependency tree. - Shortage resolution comparison: each shortage in the Shortages & Procurement panel now has an expandable options row. Clicking a shortage reveals side-by-side resolution options: buy from market (with ISK/unit and total cost), react in-house (reaction name, cost, time estimate, and ISK savings vs market), manufacture in-house, and reallocate from another order (if any active orders have the same material reserved). The cheapest or best option is marked with a ★ Best badge.
- Auto-create child production plan: added
/Industry/PlanChildren/{id}— a review page for any build order that lists every BOM material with an in-house blueprint or reaction formula, showing quantity needed vs available vs to build, blueprint, and estimated cost. Manufacture and reaction materials appear in separate sections with select-all/none controls. Confirming creates the selected child build orders in bulk. A "Plan Child Orders" button now appears on the dependency tree page. - Create build order from untracked ESI job: the Order/Job Reconciliation panel now shows a Create Order button for every untracked ESI manufacturing job alongside the existing Link option. Clicking it finds the matching blueprint profile, creates a build order priced at-cost, immediately sets it to InProgress using the ESI job's start/end dates, and links the ESI job — letting corps import jobs started outside the app without going through the full quote/approve/start flow. Returns a clear error if no blueprint profile exists for that product.
- Blueprint reconciliation panel: the Command view now shows a Blueprint Reconciliation panel whenever ESI blueprint sync has run and discrepancies are found. The panel lists ESI-only blueprints (in ESI but not the library), library-only blueprints (in the library but not seen in ESI), and ME/TE conflicts (linked pairs where efficiency values differ). Each row shows blueprint name, kind (BPO/BPC), and the specific values, with a red badge for conflicts and amber for gaps.
Fixes
- Mining-op ledger timeout and parallel cache refresh: increased mining-op mining-ledger ESI request timeouts to
15seconds and added bounded parallel fetches for both active-op direct fallback ledger pulls and the background per-character cache refresher, so one slow character response no longer serially stalls the rest of the linked-character/cache batch. - Mining-op create/join duplicate guard: saved the creator's active membership inside the locked op-create transaction before slow ledger baseline loading, made same-op Join requests idempotently route back to the current op instead of inserting another member row, and collapsed duplicate active member rows in live-op rendering so existing duplicate memberships stop showing twice.
- Mining-op linked-character resolve speed: preloads active members' linked main/alt trees once at the start of each op sync cycle, replacing repeated per-member parent/child user lookups with batched cache priming so the
resolve=portion of mining-op sync timing no longer burns several seconds per member. - Mining-op apply hot-path cleanup: mining-op sync now primes item group and ore price lookups in bulk per op, adds
applysub-timings for load/lookup/group/price/merge/save in the audit log, and stops writing ore-mined milestone events during live ledger refresh. Ore-mined milestones are now recorded once the op is confirmed, keeping active-op sync focused on ledger state instead of progress-event writes. - Mining-op payout/persist timing split: mining-op audit logs now break payout recalculation into corp/ledger/history/settings/price/math/profit/save timings and split persist/dashboard refresh into save/status/snapshot/SignalR timings. Payout calculation now reuses the op ledger's stored ore price when available and only falls back to the pricing workflow for missing prices, matching the active-op cached pricing model while avoiding repeated price formula work during every sync.
- Mining-op popout pause control: added a pause/resume button to the mining-op picture-in-picture footer so users can stop the automatic Top Miners/Ore Breakdown/Sync Health view cycling, manually switch views while paused, and resume cycling when ready.
- Mining-op popout responsive sizing: changed the mining-op picture-in-picture window from a fixed-size
340pxlayout to a viewport-filling flex layout with an explicit PiP resize handler, so the header, KPIs, cycling view, footer, and progress bar stretch to the actual popup window size instead of leaving unused blank space. - Mining-op popout right-nav click fix: prevented the view counter label from intercepting footer clicks and raised the popout nav/pause controls above footer overlays, so the right-side view arrow works reliably.
- Mining-op sync flicker guard: stopped the countdown fallback from reloading the full command deck while SignalR is connected or a sync is active/queued, and changed join/member SignalR pushes without a completed sync token to refresh only the members panel. Full command-deck refreshes now wait for completed sync pushes, preventing members from flashing in and out during ESI sync.
- Mining-op first-cycle timer anchoring: after create/join ledger baseline reads and caches ESI data, the next op update now anchors to the earliest active member mining-ledger
APIExpirationplus a short buffer instead of a blind 10-minute timer. Scheduled sync completion uses the same expiration-aware timer so the first real cycle waits until ESI can return fresh ore deltas for the initial members. - Corp wallet real division names: Corp Wallet Divisions now loads wallet division names from ESI's corporation divisions endpoint and uses those names throughout the wallet summary, per-wallet accordion, rankings, and alerts. Corp Wallet reauthorization now requests the required corporation divisions scope, and the view falls back only to neutral
Division Nlabels with a warning instead of invented placeholder names. - Corp wallet live numbers: Corp Wallet Divisions now pulls live corporation wallet balances from ESI and builds current-month credits, debits, net totals, category totals, per-wallet breakdowns, rankings, and insight values from each wallet division's ESI journal instead of static mockup numbers.
- Corp wallet division-name source: Corp Wallet Divisions now prefers the claimed corporation director's ESI token for wallet/division reads before falling back to the viewer, shows which director token is supplying the data, and clarifies when ESI returns no custom division labels because default wallet names are omitted by the divisions endpoint.
- Corp wallet division-name diagnostics: corporation division-name ESI failures now surface the actual missing-scope, authentication, role, or HTTP response message on the Corp Wallet Divisions page instead of collapsing every failure into
ESI returned null; the page also always shows which character token is being used as the data source. - Corp wallet divisions nav icon: added a dedicated ledger/division icon for the Corp Wallet Divisions sidebar link instead of letting it fall back to the generic chevron.
Fixes
- Claude error-log export API: added a Claude/Codex-facing error-log export endpoint plus a matching
Scripts/Get-ErrorLogs.ps1helper so recent server exceptions can be downloaded with the existing API-key guardrails, grouped by signature, and triaged from the workspace without opening the in-app error UI first. - Startup user-schema self-heal for corp-wallet journal flags: added a startup schema guard that ensures the new
Userscorp-wallet journal failure/reauth columns exist even if migration history drifted, and synced the EHR model snapshot so future migrations keep those fields in step with the runtime model. - Mining ledger build compatibility fixes: resolved the active mining-op cache path compile break in
LedgerProcessorby avoiding duplicate local names inside the linked-character sync loop and passing the cache service's currentisPrimary/timestamparguments when marking cached snapshots as applied. The corp-goal test fixtures were also updated to the currentDiscordServer.NameandUser.CorporationIdmodel properties so the full solution builds again. - Notification formatter hostile-state parsing: stopped
FormatNotificationfrom treatinghostileState:payloads as numeric IDs only, so values likefalseare now rendered as readable text instead of throwing a format exception during notification processing. The task processor also now catches formatter failures per notification so one malformed payload does not abort the rest of that corporation's notification batch. - Director corp-wallet journal re-auth warning: repeated
ESI returned nullcorp-wallet journal failures now count toward a dedicated five-strike threshold on the director token, automatically pause bounty wallet pulls for that character, skip monthly income wallet reads while the token is flagged, and show a warning on the Director page telling the corp to reauthorize the director's Corp Wallet scope. - Discord setup stale role/channel cleanup: hardened the Discord setup sync so missing guild roles and channels now clear or remove the dependent corp settings, title mappings, auth-role mappings, structure on-call role references, and notification alerts before the stale rows are deleted. The sync also deduplicates Discord channel rows alongside roles and now logs the full exception chain if Discord setup still fails to save.
- ESI bad-request spam control: serialized token refresh attempts per user, stopped the shared refresh guards from logging the same re-auth warning on every call, and removed the extra
BadRequestCountincrement in the task processor's bulk token refresh pass so one dead refresh token no longer explodes into a burst of duplicateGetTokenbad-request errors.
Fixes
- Corp milestones landing load time: changed the Milestones card and earned trophy lookup to read maintained
MilestoneProgressesrows instead of recalculating every active milestone from rawMilestoneEventson each load, making the dashboard milestone panel render from indexed cached progress data. - Market pricing Evepraisal HTML fallback: removed the ignored Evepraisal request from
APIGetPrice, fetches ESI buy/sell data for the actual item ID instead of the market-hub name, and treats future non-JSON Evepraisal responses as a logged null result instead of throwing a JSON parse exception. - Mining ledger per-character cache: added durable per-character mining-ledger snapshots plus op/member/source state, a background cache refresher that polls due characters independently, and a cache-first active mining-op sync path with direct ESI fallback for misses or expired snapshots so full op calculations still run on the normal cycle without every cycle pulling every character from ESI.
- Mining op picture-in-picture Razor build: escaped the embedded CSS
@keyframesrule in the mining op picture-in-picture style array so Razor no longer treats it as server code during view compilation.
Fixes
- Recruitment application ESI review UI: updated
/Recruitment/Applicationapplicant reviews to use the same Corp Member ESI hero, tab shell, and right-side status treatment, with the application status control kept inside the new review layout. The action also falls back to the user's latest active application when the link provides onlyUserId. - Recruitment apply validation retry: fixed the public corporation application form so an empty required answer still shows the validation error, but the Apply button no longer stays disabled afterward. Applicants can fill the missing fields and submit without refreshing the page.
New Features
- Switchable sidebar navigation: a Classic / Modern toggle now lives at the bottom of the sidebar. Classic preserves the existing accordion layout; Modern collapses the sidebar to a 56 px icon rail — clicking any section icon slides out a flyout panel with a live search filter. The chosen style is saved in
localStorageand restored on every page load.
Fixes
- Calendar ESI sync for CEO tokens: aligned the calendar worker's director-role check with login authorization by accepting ESI
CEOroles as valid director tokens, so CEO-claimed corporations are no longer cleared before calendar events can sync.
Fixes
- Mining-op first-cycle timer and ESI date handling: reset a newly created mining op's
NextUpdateafter the creator's synchronous ESI ledger baseline finishes, and include same-day ESI mining rows when building that baseline, so the first 10-minute scheduler window starts from the collected creator records and can see daily ledger deltas instead of filtering them out. - Industry workbench Razor build fix: replaced invalid Handlebars-style comments and fragile inline Razor control blocks in the Industry workbench, and disabled the option tag helper for that view so its conditional
selectedattributes compile cleanly.
Fixes
- Mining-op first-cycle ore visibility: changed mining-op baseline seeding to keep fresh post-join ore as a visible first-cycle delta instead of zeroing it out, and tightened ledger filtering to the actual op start time so linked-alt mining that happened during the op shows up on the first visible cycle.
- Mining-op creator auto-join regression: fixed the synchronous join helper to resolve the just-added
MiningOpMemberfrom EF's tracked state instead of immediately re-querying the database, so creating or joining an op no longer leaves behind an active op with0 membersand no joined-op view. - Mining-op join flow rebuild: replaced the background mining-ledger baseline queue with a single synchronous join-and-baseline path so mining-op creation, normal joins, invite joins, and auth-callback joins all run the same required ledger setup before the user is considered joined.
- Mining-op scheduler timing: simplified mining-op sync scheduling to process due ops one at a time, stop pre-advancing
NextUpdatebefore work succeeds, and only move the next sync window forward after a successful cycle so sync timing is easier to reason about and less likely to hide delayed work. - Mining-op alt cache behavior: stopped linked alts from extending the whole member cache window by tracking member-level mining-ledger expiration from the primary character only, while still refreshing linked alts in the same sync cycle.
- Mining-op family lookup overhead: reduced repeated mining-op sync database work by caching resolved main/alt families inside
LedgerProcessorand using no-tracking user-family lookups during ledger refresh and baseline work instead of repeatedly rebuilding the same tracked user graph. - Mining-op synchronous SignalR updates: removed extra
Task.Run(...)wrappers around mining-op sync status and SignalR update calls in the mining-op scheduler/finalization flow so the sync pipeline follows one direct execution path without hidden thread hops.
Fixes
- Mining-op sync hot path batching: reduced mining-op sync churn by batching payout price lookups per op, replacing repeated ledger-row linear scans with keyed lookups in the ledger merge path, and deferring per-member sync saves into single batched op-level saves so each scheduler cycle does less repeated work.
- Mining-op live sync status push updates: active mining-op members now receive op-scoped SignalR status updates during scheduler work, including queued/behind-other-op messages, ledger refresh, payout recalculation, dashboard refresh, and retry warnings, so they can see exactly which sync step is taking time and report the stalled stage text if updates stop moving.
- Mining-op invite link sync flicker: the active mining-op invite URL now restores from an op-scoped client cache during partial refreshes instead of hiding and re-fetching on every sync-driven rerender, so the invite link no longer flashes in and out while the op is updating.
- Mining-op confirmations/pending-orders loader routing: restored the legacy inline dashboard
PendingOrdersblock to load from/Home/PendingOrders/while keeping the dedicatedMining Op Confirmationspage on the new/Home/MiningOpConfirmationQueue/endpoint, so both surfaces render again instead of going blank after the page split. - Mining-op end transition false error toast: ending an operation now cancels any stale active-op retry chain on the client before the fallback refresh runs, so the UI can return to the
Start a New Operationpanel without also surfacing a bogus "unable to load/join" mining-op error toast. - Mining-op confirmations split into a dedicated queue: mining op confirmations now have their own page, with ready-to-confirm operations separated from recently ended operations still pending final processing. The queue now makes reconciliation wait states visible instead of hiding those ops, and
Confirm Allnow confirms only the ready operations while leaving still-processing ops in the queue. - Mining-op confirmations queue ordering: the dedicated mining-op confirmations page now shows the
Pending Final Processingsection first so officers see waiting reconciliation work before the ready-to-confirm actions. - Mining-op ore-id lookup cache: promoted the
Type_Id -> OreIdmapping used by ledger sync into a shared 24-hour memory cache so repeated mining-op sync runs no longer rebuild the ore lookup dictionary from the database each time a newLedgerProcessorinstance is created.
New Features
- Preview-only industry pricing controls: exposed the existing corp
Orderspricing mapping as anIndustrypricing system in the setup wizard and pricing-control assignment UI, while keeping visibility and saves hidden from regular users until the workflow is ready for wider rollout. - Industry source in the price builder: price formulas can now use a preview-only
Industrystep alongside manual and market inputs, allowing other corp pricing formulas to consume the Industry pricing system as a reusable source. - Industry workbench foundation: added a preview-only
/Industryworkbench with shared cost-profile editing, structure-profile storage, item search, and a manual material-cost registry to start the Reactions and Industry ERP implementation on real persisted models instead of planning docs alone. - Preview-only reaction planning workbench: the Industry page now supports manual inventory rows, manual reaction formulas, and live reaction opportunity calculations that price inputs from the corp Industry source and show run counts, costs, margin, and ISK/hour against the current shared inventory snapshot.
- Industry reaction persistence migration: added EF migration
20260412210335_20260412_AddIndustryReactionWorkbenchso the new industry cost, structure, inventory, and reaction workbench tables can be created through the normal schema path. - Migration drift split: separated the pre-existing PI/cache schema drift into
20260412210333_20260412_SyncPiCacheFieldDriftand20260412210334_20260412_AddCharacterRouteCachesso the following industry/reaction migration stays focused on the new preview-only workbench tables. - Preview-only manufacturing definitions + quote builder: the
/Industryworkbench now supports manual blueprint/BOM profiles with product search, ME/TE, run counts, preferred manufacturing structures, and a live build-quote preview that prices materials from the corpIndustrysource and shows slot load, ETA, margin, and material shortfalls. - Preview-only industry order workflow: added a first-pass corp manufacturing queue with quoted orders, approval-time material reservations, manual payment confirmation/waiver, queued/running/completed/delivered state flow, and a visible order board on
/Industryso the Industry.md workflow can start operating as a real prototype instead of only a calculator. - Industry manufacturing persistence migration: added EF migration
20260412214333_20260412235900_20260412_AddIndustryManufacturingOrdersfor blueprint profiles, BOM rows, build orders, reservations, jobs, and new manufacturing margin/trust-mode cost-profile fields. - Reaction-aware BOM pricing + shortage board: build quotes now support per-input cost-source preference (
market,in_house_reaction,manual), show which source was actually used per BOM line, flag market fallbacks when no current reaction cost exists, and surface a new preview-only shortage board with procurement actions for blocked materials. - Industry shortage persistence migration: added EF migration
20260413011602_20260413001500_20260412_AddIndustryShortagesAndCostSourcesfor BOM source preferences and persistent shortage/procurement entries tied to industry orders. - Child build orders from shortages: the preview-only shortage board can now spawn linked child manufacturing orders for missing in-house components, and the
/Industryqueue now shows parent/child order relationships plus open child-work counts before a parent job can start. - Industry child-order link migration: added EF migration
20260413013912_20260412_AddIndustryChildOrderLinksfor parent-order links and shortage-to-child-order references onindustrybuildorders, keeping multi-step manufacturing chains persisted in the normal schema path. - Preview-only blueprint BOM import: the
/Industryblueprint draft form can now import manufacturing materials from Fuzzwork for the selected product item, prefilling BOM lines in the preview-only workbench so new build profiles no longer have to start from manual line-by-line entry. - Preview-only blueprint library: the
/Industryworkbench now includes a separate owned-blueprint registry where privileged setup users can record actual corp BPO/BPC holdings, structure scope, location notes, ME/TE values, and remaining runs for quotes and jobs. - Owned blueprint availability migration: added EF migration
20260413020015_20260412_AddIndustryOwnedBlueprintLibraryfor the newindustryownedblueprintstable and stored selected-blueprint details onindustrybuildorders.
Fixes
- Industry pricing recursion guard: the pricing engine now detects circular/self-referential Industry formula usage and falls back cleanly to cached market pricing instead of recursing through formula evaluation.
- Industry manual-cost price sourcing: the Industry pricing path now checks the new corp material-cost registry first, so manual in-house prices can immediately drive preview-only corp pricing experiments before the full reaction and manufacturing pipelines are completed.
- Industry preview gating: removed the hardcoded
Ascorbiclock from the industry workbench and pricing controls, replacing it with a preview-only gate for privileged setup/director users while regular users remain unable to see the work in progress. - Reaction structure matching and costing: reaction opportunities now choose the best eligible Athanor/Tatara profile per formula, apply the matching cost profile, and clearly flag missing structure eligibility instead of assuming a single default reaction structure can run every formula.
- Blocked-order auto recheck: saving or deleting manual inventory now re-evaluates blocked industry orders, resolves shortage entries when materials are covered, and automatically moves recovered orders back to a quoteable state instead of leaving them stale after stock changes.
- Industry inventory state roll-forward: completing a build now consumes input inventory rows, records produced output back into shared corp inventory, and rechecks blocked parent orders so internal child production can actually satisfy shortages instead of leaving stock math stale.
- Real blueprint availability checks: industry quotes and build starts now resolve against the best matching owned blueprint for the assigned structure, block when no usable corp blueprint exists, and consume BPC runs from the selected owned blueprint instead of the recipe profile.
- Mining-op sync hot path batching: reduced mining-op sync churn by batching payout price lookups per op, replacing repeated ledger-row linear scans with keyed lookups in the ledger merge path, and deferring per-member sync saves into single batched op-level saves so each scheduler cycle does less repeated work.
- Mining-op live sync status push updates: active mining-op members now receive op-scoped SignalR status updates during scheduler work, including queued/behind-other-op messages, ledger refresh, payout recalculation, dashboard refresh, and retry warnings, so they can see exactly which sync step is taking time and report the stalled stage text if updates stop moving.
- Mining-op invite link sync flicker: the active mining-op invite URL now restores from an op-scoped client cache during partial refreshes instead of hiding and re-fetching on every sync-driven rerender, so the invite link no longer flashes in and out while the op is updating.
- Mining-op confirmations/pending-orders loader routing: restored the legacy inline dashboard
PendingOrdersblock to load from/Home/PendingOrders/while keeping the dedicatedMining Op Confirmationspage on the new/Home/MiningOpConfirmationQueue/endpoint, so both surfaces render again instead of going blank after the page split. - Mining-op end transition false error toast: ending an operation now cancels any stale active-op retry chain on the client before the fallback refresh runs, so the UI can return to the
Start a New Operationpanel without also surfacing a bogus "unable to load/join" mining-op error toast. - Mining-op confirmations split into a dedicated queue: mining op confirmations now have their own page, with ready-to-confirm operations separated from recently ended operations still pending final processing. The queue now makes reconciliation wait states visible instead of hiding those ops, and
Confirm Allnow confirms only the ready operations while leaving still-processing ops in the queue. - Mining-op confirmations queue ordering: the dedicated mining-op confirmations page now shows the
Pending Final Processingsection first so officers see waiting reconciliation work before the ready-to-confirm actions. - Mining-op ore-id lookup cache: promoted the
Type_Id -> OreIdmapping used by ledger sync into a shared 24-hour memory cache so repeated mining-op sync runs no longer rebuild the ore lookup dictionary from the database each time a newLedgerProcessorinstance is created.
New Features
- Topbar app version badge: added a visible version badge to the upper-right top bar so the currently running app build is easy to confirm from any page.
- Publish-script auto version bump:
Scripts/Create-PublishZip.ps1now increments the app patch version inProperties/AssemblyInfo.csevery time the publish script runs, keeping packaged releases moving forward automatically without a separate manual edit.
Fixes
- Landing mining-op links: corrected the landing page mining-op CTA and related landing activity link to use
/Mining/MiningOpsinstead of/Home/MiningOps, so they open the full mining command page rather than a partial-only route.
Fixes
- Mining-op ledger timeout hard cap: hard-coded mining-ledger ESI requests to a
5second timeout so join/start flows fail fast on stalled ESI responses instead of waiting up to45seconds per character before continuing. - Bounty sync journal paging + timing diagnostics: bounty recording now fetches corp wallet journal pages with a short request timeout, stops paging once entries are older than the current bounty cutoff instead of re-reading the full journal every hour, uses a direct
CharacterId -> UserIdlookup during pilot aggregation, and logs per-corp bounty durations so slow corps are easier to identify from task heartbeat follow-up logs. - Mining-op join reliability + async ledger baseline: first-time joins now save the new member as active immediately, avoid null user navigation during baseline setup, and move per-character mining-ledger baseline initialization onto the background queue instead of blocking the join request until every linked character finishes or times out.
- Mining-op cumulative totals across active-op panels: the active mining-op ore breakdown, top-level gross value, member contribution values, refined totals, and snapshot-backed cards now all use cumulative
OpAmountvalues for the full op instead of mixing whole-op totals with last-sync deltas. - Mining-op viewer alt breakdown completeness: the
My Orescharacter breakdown now resolves the viewer’s full main/alt family and includes linked-character ledger attribution rows, so separately joined alts no longer disappear from the viewer summary. - PI storage volume lookup by current DB group IDs: fixed PI storage bars still showing
0 m³when cached launchpad contents used the local item import’s PI type IDs and group IDs. The PI page now recognizes the currentitems.group_idPI groups when resolving content volume, so launchpad/storage usage falls back to real PI volumes instead of zero, and the legacy launchpad/storage type-ID fallbacks now cover the actual PI structure variants in the local dataset.
Fixes
- PI storage-capacity totals: corrected PI storage facility capacity from
500 m³to12,000 m³in the storage-summary math so planet storage bars, used/capacity totals, and fill percentages now reflect the real storage facility capacity instead of showing inflated utilization. - PI POCO tax compile guard: initialized the PI storage-summary POCO export tax fallback rate before the
TryGetValue(...)branch soPlanetaryIndustryControllerno longer hits aCS0165"use of unassigned local variablepocoTaxRate" build failure. - Landing recent completed-op detail strip:
Home/Landingnow shows higher-signal summaries on recently completed mining ops, including confirmation state, total op value, payout/tax/fleet split (when present), and the ended timestamp so leaders can scan outcome details without opening the full op view. - Recruitment mail paging + cache acceleration:
Recruitment/GetMailsnow caches each viewed member mailbox for a short TTL, only resolves sender names for the currently rendered page, and serves the inbox in paged slices (25rows by default) instead of server-rendering every mail at once. The Recruitment CurrentMember mail tab now lazy-loads older pages with aLoad Older Mailaction,Recruitment/GetMailBodycaches recruiter-side body lookups for repeated opens, and both endpoints log cache-hit/fetch timing so slow ESI or oversized inboxes are easier to spot in production.
New Features
- PI full-image command-center mockup: added
mockups/pi-v1-full-image-command-center.html, a high-density PI dashboard concept showing colony health queue, route/feedability status, per-factory run/ETA forecasting, projected output value, pickup-tax estimates, and data-confidence indicators in one responsive view.
Fixes
- PI factory/reaction colony visibility: colony status now evaluates all timed PI pins (
ExpiryTime) rather than extractor-only timers, so factory-only reaction planets are now classified asActive,Expiring, orExpiredwhen running instead of being forced intoIdle. - PI topbar status chip parity: dashboard PI counters now include any timed PI pins (extractors plus factory/reaction cycles), so factory-only active colonies are reflected in the topbar status totals.
- PI card clarity for non-extractor colonies: factory-only colony cards now show a dedicated
Factory Timerssection when timers exist, and idle copy was updated so reaction-only setups no longer look like missing data. - Refresh-user server load control + runtime improvements:
TaskProcessor.RefreshUserInfonow uses bounded parallel execution for Discord role updates and token refresh processing (configurable viaRefreshUserInfoRoleUpdateParallelismandRefreshUserInfoTokenRefreshParallelism), token refresh runs against detached user snapshots to avoid cross-thread EF tracking contention, and token/affiliation helper paths were trimmed to remove redundant async scheduling and batching overhead. - Refresh-user Discord deep optimization pass: the refresh job now preloads Discord auth/title/scope config once per run, snapshots each guild once per server instead of repeatedly re-fetching role member lists, only runs title-role sync when mappings exist and the lower-frequency title cadence is due (
RefreshUserInfoTitleSyncIntervalHours, default4), skips bad-token users during title sync, reuses guild snapshots for recruitment cleanup, and logs per-phase/per-server timing and count metrics to make the long tail visible in production. - Notification sync runtime reduction:
GetNotificationsnow uses an async ESI notification path, fetches director and alt notification feeds with bounded parallelism (GetNotificationsFetchParallelism, default3), skips corps with no selected notification types before any ESI work, deduplicates merged notifications before formatting/broadcast, reuses cached name/item/price lookups across a corp batch, batchesLastNotificationChecksaves, and logs per-corp fetch/unique/new/sent counts with durations. - Mining-op scheduler fairness + heartbeat diagnostics: due mining-op work is now ordered by overdue
NextUpdatetime instead ofMiningOpId, mining-ledger timeout enforcement now uses the requested timeout path instead of silently falling back to an unlimited wait, and task heartbeat output now includes per-tasklongestDurplus an overallLongestTasksummary for production diagnostics.
Fixes
- Corp goal lifecycle auto-updates from incoming events:
MilestoneService.RecordEvent(...)now evaluates active corp goals for the event’s corp/metric and runs completion + milestone-threshold checks immediately, soCompletedUtcand 50%/75% notifications stay current without manual recalc. - Corp goal detail duplicate leaderboard computation:
HomeController.CorpGoalDetailnow reuses the already-loaded full leaderboard to resolve the current user row, removing a second full-board recomputation path. - Corp goal detail "you" highlighting reliability: leaderboard JSON now includes
userIdand the page matches rows against the viewer’s resolved main user id from the controller instead of fragile name-based matching. - Corp goal custom-range validation edge case: creating a
CustomRangegoal now rejectsstart == end, aligning with exclusive end-bound filtering (OccurredUtc < WindowEndUtc) so zero-length windows are not accepted.
New Features
- Corp Goal detail page: clicking a goal card on the Corp Goals list now opens a full detail view at
/Home/CorpGoalDetailshowing a stat strip (progress, remaining, contributor count, your rank), a full paginated leaderboard with alt-account counts, a recent events feed (last 50 contributions), and your personal position row highlighted inline. Admins see Recalculate and Enable/Disable buttons directly from the detail page. - Corp Goal Setup redesign: the setup form now uses a sectioned layout with a visual metric icon picker, a window tab bar (Lifetime / This Month / Rolling 30d / Custom), and autocomplete search fields for ship-type, victim-corp, and ore-type filters. The existing goals panel shows all goals in a card grid with per-card controls and a direct "View Detail" link.
- Corp Goals card navigation: each goal card on the Corp Goals list is now clickable and loads its detail page inline.
Fixes
- Mining-op alt re-auth targeting:
MiningOpAdAltnow resolves the requested warning-row alt within the current main+alt family and carries that expected user id in OAuth state, so re-auth links no longer silently target the wrong character. - Mining-op callback character guard: the
MiningOpScopecallback now validates the authorized character against the expected state user when provided and returns a clear error if a different character is authorized. - Mining-op auth warning panel cleanup: auth warning rows now describe mining-auth access requirements (instead of always saying “missing scope”), and clicking
Re-authorize altnow removes that specific row immediately before redirect to avoid stale warning carry-over. - Token refresh persistence reliability: refresh success now writes token fields to the tracked DB user row, preserves the existing refresh token when SSO omits
refresh_token, and syncs the caller copy from persisted values. This removes intermittent post-refresh auth drift that could make mining warnings reappear.
New Features
- Corp Goals: new corp-wide progress-tracking system. Officers create goals (ore mined, ships killed, bounty ISK, fleet participation, logistics) with configurable time windows and per-metric filters. A member-facing card grid at
/Home/CorpGoalsshows live progress bars and per-goal leaderboards with alt roll-up. Goal completion fires a Discord embed to the notifications channel; crossing 50% or 75% sends a milestone ping tracked viaLastNotifiedPctto prevent duplicate messages. - Corp Goal Setup: admin UI at
/Home/CorpGoalSetup(requiresLoyaltySetuppermission) for creating, enabling/disabling, deleting, and recalculating goals. Supports ore-type, victim-corp, ship-type, and fleet-type filters, and window types: lifetime, current month, rolling 30 days, or custom date range. A "Recalculate" button recomputes completion state from existing milestone events. - Fleet and logistics milestone event emission: fleet confirmations now emit
FleetsParticipated,FleetMinutes, andFleetsLedmilestone events (with fleet type); completing a logistics order emitsLogisticsOrdersCompletedandLogisticsVolumeHauledevents — both feed directly into Corp Goal progress.
Changes
- Corp Goals navigation wiring: added
Corp GoalsandCorp Goal Setupto theCorp Managementsidebar navigation so both pages are directly reachable from the main menu.Corp Goal Setupremains gated by the existingLoyaltySetuppermission. - Corp Goals migration-registration fix: added missing EF migration metadata for
20260403000010_20260403_AddCorpGoalsand20260403000020_AddCorpGoalLastNotifiedPctso EF now discovers and applies the corp-goal schema migrations. This resolves runtimeTable 'evehr.corpgoaldefinitions' doesn't existerrors when clicking the new Corp Goals buttons. - OAuth scope re-auth hardening:
BaseController.BuildFlowAuthorizationUrl(...)now automatically unions requested scopes with the user’s persisted DB scope set before building EVE SSO auth URLs, then normalizes/deduplicates the combined list. Re-auth flows now consistently request full existing scopes plus newly required scopes across controllers (with claim-scope fallback if DB scope lookup is unavailable). - Contract-validation prompt parity fix: updated the bulk-validation (
ValidateAllOrders) contract-item failure branch to call scope/relog prompt builders with the active user context, keeping prompt routing and scope composition aligned with per-order validation behavior. - Anti-forgery live diagnostics page: added an Ascorbic-only probe at
/Home/AntiForgeryProbewith both form POST and AJAX POST checks against a[ValidateAntiForgeryToken]endpoint (/Home/AntiForgeryProbePost) to verify live anti-forgery behavior directly. - Pending confirmation contract-match dedupe fix:
HomeController.ValidateOrderandValidateAllOrdersnow normalize merged corp+character contract feeds to uniquecontract_idrows and match onlyoutstandingcontracts before issuer/value comparison. This prevents falseMultiple orders found with the same user and value.results when the same contract appears in both ESI sources or when older non-outstanding contracts share the same amount. - Anti-forgery rollout to high-use account/order actions: added explicit
[HttpPost]+[ValidateAntiForgeryToken]protection toOnVacation,ValidateOrder,ValidateAllOrders, andMergeUserPointsToMain; updated Dashboard/User Management AJAX calls (OnVacation,SaveGateway,Validate,Verify All,Merge Alt Points) to send__RequestVerificationTokenin both headers and form body for proxy-safe validation.
Changes
- Task processor performance tuning: optimized
TaskProcessor.Bountieswith no-tracking read queries and lean corp/director projections, and optimizedTaskProcessor.RefreshUserInfoby skipping empty-user affiliation API calls, collapsing duplicate per-server saves into one conditional save, replacing per-user corp-id DB lookups with in-memory corp/user maps, batching Discord auth-role lookups per server, and skipping empty role-affiliation API calls. - Refresh-user affiliation lookup caching:
TaskProcessor.RefreshUserInfonow caches character affiliation results per refresh run and reuses them across server/member-role passes, reducing duplicateTryGetBulkCorpAffiliationAsync(...)calls for repeated characters in the same cycle. - Debug log-noise reduction: added a development logging override for
Microsoft.EntityFrameworkCore.ChangeTrackingatWarning, suppressing repetitive EF Core tracking debug messages (for exampleContext ... started tracking 'AuditLog' entity) from local debug output. - Mining-op alt mining-scope remediation: missing mining-scope ledger warnings now surface an actionable toast that names the affected alt and includes a direct re-authorize link, and the
MiningOpScopecallback now accepts re-auth for already-linked alts so the missingesi-industry.read_character_mining.v1scope can be refreshed instead of being rejected as a duplicate alt add. - Mining-op startup warning push: operation-launch baseline ledger initialization now forwards warning messages to op members immediately through SignalR
hasError(instead of only writing server logs), so missing/invalid alt mining scope issues are visible right after launch and can be fixed before the next sync cycle. ManualJoinOpnow pushes the same warnings immediately. - Mining-op on-screen auth warning panel: mining-scope alt failures now also render as a persistent warning list inside the active Mining Command Deck (not just toastr), now with clear per-alt
Re-authorize altandDismissbutton actions so scope re-auth and acknowledgment are obvious and clickable. - Mining-op miner-contributions alt health expander: each main miner card now includes an expandable alt section that shows only that main’s linked active-op alts with explicit health state. Successful ESI responses (including valid empty-ledger returns) show as
Healthy; failed syncs are marked orange with per-altRefresh ESIaction buttons. - Toast readability + size guardrails:
toastr8messages now wrap cleanly with no message-area scrollbars and are clamped to a fixed multi-line max height, preventing oversized toast boxes on long errors. - Contract-validation scope re-auth loop hardening: contract-scope authorization links now carry the requesting user id in OAuth state, and the
CorpContractsScopecallback now verifies the authorized character matches that requesting user. Wrong-character authorizations now return a clear expected-character error instead of silently updating another user and looping back toScope Required. - Contract-validation auth return routing: order-verification
Authorize ScopeandRelog Nowprompts now return toHome/OpConfirmationsafter auth/login instead of redirecting toHome/Dashboard. - Milestone roll-up repair + nested-alt support: milestone roll-up now walks the full linked-character tree, including nested alt chains created by main swaps. Toggling milestone roll-up now repairs and rebuilds milestone progress rows for the whole family to clear stale or duplicate progress data, and roll-up failures return a clear milestone-specific error instead of a vague generic toast.
- Milestone setup error clarity + logging hardening: create/toggle/delete actions in
Milestone Setupnow show the real server-provided error (including anti-forgery/session failures) instead of only generic toasts, andCreateMilestonenow performs explicit input validation and returns/logs clear database/runtime failure context when creation fails. - User Management publish-style resilience: added a scoped fallback stylesheet directly in
Views/Home/_UserManagement.cshtmlfor the redesignedum2-*layout so the page remains fully styled even if a publish target has a stale or missingContent/views-consolidated.css. - User Management milestone roll-up toggle request hardening: the roll-up toggle now includes a local anti-forgery token scope and sends explicit anti-forgery headers/context on
/Home/SetMilestoneRollupToMain/requests, plus safer client JSON error parsing. This prevents generic toggle failures when stale cached/global script state does not inject anti-forgery headers automatically. - Milestone roll-up toggle no longer depends on milestone setup/repair success:
SetMilestoneRollupToMain(...)now saves the roll-up setting first, then runs milestone-definition checks and family progress repair as best-effort follow-up steps with isolated logging. Users can now enable/disable roll-up even when no milestones exist, or when milestone repair fails temporarily. - User Management milestone roll-up anti-forgery transport fallback: the roll-up toggle now sends
__RequestVerificationTokenin both header and POST form body. This avoids persistent 400 request-validation failures in environments where underscore-prefixed custom headers are stripped by proxies/load balancers. - User Management milestone roll-up live hotfix: anti-forgery validation was disabled specifically on
SetMilestoneRollupToMain(...)to bypass live-only request-validation failures while keeping authenticated-user checks, and the client now appends HTTP status details when no JSON error payload is returned. - Contract-validation re-auth loop guard: contract
403errors are now classified more precisely. Scope re-auth prompts appear only for explicit missing-scope/token-scope failures, while role-related403responses (for example missing Director/CEO role) now return a direct role-permission error instead of repeatedly looping users throughAuthorize Scope.
Fixes
- Pending confirmations bulk-verify no-op fix: top-level
Confirm All/Verify Allactions now render only for users withConfirmOperationspermission, and theValidateAllOrdersclient handler now validates response shape before processing so unauthorized/non-JSON responses and empty pending-order sets return clear toast feedback instead of appearing to do nothing. - Build recursion/MSB3030 fix: excluded
EveHumanResources.Tests\**\*from web-project content discovery so nested test output folders are no longer copied as website content, preventing recursiveartifacts\publish\...copy paths and the missing-fileMSB3030failure during build. - Scope-required dialog button polish: cleaned up confirmation-dialog button rendering by overriding jQuery UI focus artifacts, keeping button labels centered and no-wrap, and adding responsive wrap behavior for smaller screens so
Authorize Scope/Laterlook and behave consistently. - Pending confirmation count mismatch fix:
ValidateAllOrdersnow applies the sameIsMiningOpReadyForConfirmationreadiness filter used by the pending-confirmation list views, soVerify Allno longer reports failures for hidden not-ready operations. - Verify All loading-state spinner: the
Verify Allaction now shows an inline spinner +Verifying...label, disables the button witharia-busywhile the request runs, and restores the original button state on completion. - Login callback latency improvement: login callback handling no longer waits on per-alt token refresh checks. Alt token refresh is now queued to a scoped background task so users can complete login without waiting for every linked alt token refresh call.
- New-corp bootstrap moved off login critical path: first-time corp setup records (
PricingTypeMappings,SystemSettings,UserPoints,LogisticSettings) are now initialized by a scoped background bootstrap task instead of blocking callback login, and newly created corps are redirected directly toSetupWizardafter sign-in. - Login callback side-effect offload: additional non-critical callback work now runs in deferred background processing after sign-in, including profile/name sync, corporation metadata lookup/enrichment, old-corp permission and Discord member-role cleanup on corp changes, and recruitment application side-effects (
CorpApplicationcreation, answer ownership updates, and broadcast). - Mining-op ledger refresh reliability: mining-ledger ESI calls now use a bounded configurable timeout (
AppSettings:MiningOpLedgerRequestTimeoutSeconds, default 45s) instead of an infinite wait, and scheduler failures in mining-op update processing now requeue active ops for a near-term retry (2 minutes) instead of waiting a full normal cycle after an error. - Mining-op scheduler tuning for 4-vCPU hosts: reduced mining-op scheduler parallelism from
3to2and mining-op finalization parallelism from2to1to reduce CPU burst spikes while keeping active-op refresh processing running. - Mining-op start latency reduction: clicking
Launch Operationno longer waits for synchronous main/alt baseline mining-ledger loads before returning. Initial baseline ledger loading now runs in a background task so the running-op page can render immediately while baseline sync completes. - Mining-op linked-ledger fetch pacing: active mining-op refresh now prefetches main and alt ledger API calls in parallel during member updates (configurable with
AppSettings:MiningOpLedgerFetchParallelism, default4), removing per-user sequential API-call gaps inside each refresh pass. - Mining-op UTC time display: standardized mining-op timestamp rendering across active-op, current-ops, pending confirmations, op history, and unconfirmed-op member rows to explicit UTC game time labels (for example
HH:mm UTC) using UTC-safe conversion for unspecified DateTime kinds, eliminating local-time drift on the page. - Mining-op linked-ledger fetch strategy update: removed linked-user prefetch parallelism from mining-op update processing, so ledger API calls now execute sequentially per member with no intentional delay between calls. Removed the now-unused
AppSettings:MiningOpLedgerFetchParallelismsetting from appsettings files. - Mining-op sync refresh fallback + SignalR sync token fix: corrected SignalR mining-op sync-token reads to use the current
.MiningOpTime[data-lastsync]banner attribute (with legacy markup fallback), and added an overdue-cycle fallback refresh so once the countdown reachesawaiting sync..., the client periodically requestsGetJoinedOp()until a fresh sync arrives even if push delivery is delayed or missed. - Mining-op non-threaded
UpdateOpperformance pass: kept mining-ledger API calls sequential and always fetched fresh ESI ledger data per linked-user check, while retaining local non-ESI caches for per-item sale-price resolution and item-group lookups used by ore filtering. This preserves freshness while still reducing duplicate local compute.
Fixes
- Google ads + consent flow hardening: added explicit Funding Choices loader before AdSense script load, parameterized publisher client ID via app settings, and normalized ad slot/client usage to one canonical source.
- Google consent persistence improvements: consent hook now caches/replays last consent mode values in local storage, ingests Funding Choices consent mode values when available, and deduplicates consent-update calls to reduce repeated consent churn (especially on mobile).
- Patron ad suppression hardening: ad vendor script loading now respects patron checks consistently (including Infolinks gating),
AdBlocker()always returns non-block for patrons, and patron resolution now has DB-backed fallback refresh to avoid stale auth snapshots showing ads for patron corps. - AdSense-only runtime + live-cycle ad refresh: removed remaining Infolinks script injection, retired the unused Infolinks app-setting toggle, added reusable ad-slot render helper for AJAX partials, and wired live Mining Op/Fleet panel ad slot re-render after cycle refreshes.
- Patron ad eligibility claim-drift fix: patron checks now validate live user-to-corp mapping when claim corp data is stale and refresh auth snapshots from the resolved corp, preventing ads from showing to patron-corp users with outdated claims.
- Validation scope prompt modal: pending-order
Validate/Verify Allnow returns explicitScopeRequiredresponses and shows an authorize modal action instead of hard redirects or raw ESI errors. - Pending-order validation 500 hardening: fortified validation paths against null scopes, missing issuers, and contract-item scope failures with non-throwing logging, so failures return stable JSON responses instead of internal server errors.
- Pending-order validation UX hardening: replaced raw ESI endpoint failure text in user-facing toasts with friendly contract-validation guidance (expired login, missing scope, temporary ESI outage, delayed contract visibility) while keeping raw details in server logs.
- Contract validation scope policy update: pending-order validation now requires both corp and character contract scopes for validating directors/CEOs, and re-auth prompts now request both scopes together.
- Mining-op final reconciliation gating: ended ops now hold for a reconciliation delay window before final ledger+payout processing, and pending/confirm flows require post-delay reconciliation completion to prevent early confirmations on pre-final totals.
- Mining-op missing-ore miner alert: mining-op member UI now shows a visible warning banner when unmapped ore rows are detected, including ore names/type IDs and guidance to contact Ascorbic for mapping correction.
- Mining-op finalization durability + auto-end parity: removed non-durable finalization task dispatch from manual end flow, moved final reconciliation to scheduler-driven retryable processing, and aligned auto-ended member closeout behavior with manual-end finalization pipeline.
- Mining-op payout undercount fix for linked miners/alts: payout aggregation now resets existing history amounts before recompute and sums deltas by
(UserId, MarketId), preventing main+alt same-ore rows from overwriting each other and undercounting totals. - Mining-op leave final-sync wait fix: leave-op flow is now async and correctly awaits remaining API cache-expiry time before the final member ledger refresh.
- Mining-op new-ore baseline update: newly observed ore rows now initialize with
PriorAmount=0so first appearance contributes immediately to op totals. - Mining-op regression tests: added tests covering payout delta aggregation, final-reconciliation delay/threshold policy, and first-seen ledger row initialization to guard against regressions.
Changes
- zKill ingestion API migration: moved from RedisQ polling to the R2Z2 sequence API and updated listener parsing to consume sequence payloads with embedded ESI killmail and ZKB metadata.
- zKill listener behavior tuning for R2Z2: added explicit handling for R2Z2 polling semantics (404 no-new-mail wait, 429 retry/backoff with
Retry-After, preserved 403 cooldown handling) and sequence iteration updates per processed payload. - zKill config update: websocket bootstrap now passes R2Z2 base URL and polling delay settings from app config, with defaults added to standard and development appsettings.
- Recruitment Applications page refresh: rebuilt the applications view into a cleaner tabular review layout, restored per-application status dropdowns, and added resilient portrait/name fallbacks for missing user records.
- Recruitment application-detail status restore: restored the status dropdown control on the application detail page so reviewers can update status inline again.
- Recruitment status-change handler hardening: status change JavaScript now accepts both
dataanddata-ididentifiers and restores previous selection if update requests fail. - Discord owner reset command: added owner-only Discord-link reset support via both slash and text commands, with a reset service that removes guild-scoped Eve-HR Discord linkage records so setup can be cleanly re-enabled.
- PI alt refresh reliability + stale fix: PI refresh eligibility now requires usable PI auth state, linked alt refresh cadence is accelerated when mains open PI, background sync excludes revoked/empty-token users, and stale-for-days scope gating from unrelated scope-invalid flags was removed so PI refreshes depend only on PI scope plus token/revocation state.
Fixes
- Google Consent Mode v2 implementation (UK/EEA/CH): replaced the temporary country-block approach with proper consent defaults in shared layout and landing page using
gtag('consent','default', ...)forad_storage,analytics_storage,ad_user_data, andad_personalization(granted baseline plus denied regional override withwait_for_update), so ads can still run while consent state is respected. - Google tag/config hardening for consent flow: moved Google tag bootstrap earlier in page render (before ad calls), made Analytics tag ID and consent wait configurable via app settings (
GoogleAnalyticsTagId,GoogleConsentWaitForUpdateMs), and added an app-setting toggle for Infolinks loading (EnableInfolinksAds). - Patreon button label typo: corrected the topbar CTA text in shared layout from
PATRONtoPATREONso branding matches the destination link. - PI stale-status refresh fix: corrected background PI sync scope filtering to use
esi-planets.manage_planets.v1, added pin-cache staleness checks so outdated pin rows are force-synced even when planet cache rows appear fresh, and added fast retry scheduling when planet list refresh succeeds but per-planet pin detail refresh fails. - Ad-blocker detection + support prompt: added a polite, dismissible ad-block notice for non-patron users in shared layout that asks users to whitelist
eve-hr.comor support via Patreon when ad blocking is detected. Detection uses a bait-node probe plus ad-slot/script fallback checks, with local throttling (once/day display, 30-day dismiss) and existing server-side once-per-day gating viaHomeController.AdBlocker. - Mining Op join first-click reliability: removed client-side 45-second timeouts from the join transition (
JoinOprequest +GetJoinedOprefresh) and hardenedGetJoinedOpoption/callback queueing so overlapping refreshes cannot drop the join completion callback or downgrade active-op retries, preventing cases where users had to refresh before join state appeared. - jQuery hardening pass (security + reliability): refactored shared jQuery helpers in
EHR.jsto render error alerts safely (no raw HTML injection), handle close actions without log IDs, and resolve anti-forgery tokens more reliably by preferring the layout-scoped global token plus scoped fallbacks while respecting request-level token headers. Updated SignalR error wiring to pass structured payloads (message + optional id) into the safe renderer. Hardened mining-op refresh error handling inDashboard.jswith retry-awareGetJoinedOpbehavior and fixed countdown drift by recalculating current time each interval tick. - zKill RedisQ 403 cooldown handling: updated
ZKillListenersoHTTP 403 Forbiddenresponses now trigger a listener-wide cooldown (ForbiddenRetryPause, default 1 minute) before additional polls are attempted, reducing repeated forbidden spam and backing off request pressure when zKill throttles RedisQ queue listeners. - Exception detail propagation for diagnostics: added shared exception-chain formatting in
LogsoLog.Error(Exception,...)records nested inner exception messages/stacks by default, updated process-capture error propagation and mining-op member save warnings, added exception-aware JSON error helpers for controller catch handlers, and updated the global exception filter development payload to include inner-exception chains. Surfaced warning/JSON error details now include actionable root-cause context instead of only top-level exception text. - Mining-op join flow hardening and invite link routing: Discord mining-op announcements now post the direct per-op invite URL (
/MiningOp/{code}) as the primary join link (with Current Operations as backup). Server join handling now validates active-op/corp access on/Home/JoinOp, treats repeated same-op joins as idempotent success, blocks second active-op joins with a clear message, and returns proper error-page output for invite-link join failures. Scheduler next-update anchoring was also normalized to a true 10-minute cadence so first-cycle and subsequent API refresh timing aligns with expected mining-op checks.
Fixes
- Mining Op create double-submit guard: hardened create-op flow in both UI and server so rapid repeated clicks can no longer create multiple operations.
_MiningOpStartnow sets in-flight state and disables the launch button immediately on click, andHomeController.MiningOpCreationnow enforces a server-side active-op guard inside a creation lock before insert. - Slot Machine cross-corp spin availability: switched slot prize-code availability and spin code draw to a shared site-wide inventory pool instead of per-corp checks, so all corps can spin whenever codes exist. Code uploads remain restricted to
Ascorbiconly, with global duplicate-code protection and updated status/admin messaging to match.
Changed
- Price Control load performance: optimized
Setup/SetPricesto use no-tracking reads, removed write-on-read mapping inserts during page load, and now synthesize missing pricing-system mappings in-memory for UI defaults so opening Price Control no longer performs DB writes. - Price Control first-render latency: preloaded the first calculator's formula rows into the initial pricing partial render (`_SetPrices` + `_PriceCreator`) and removed the extra first-load formula fetch request, reducing initial request churn.
- Price Rules item-name recovery:
GetPriceRulesnow resolves missing names via ESI universe item lookup when a ruleMarketIdis absent from localItems, and caches recovered rows intoItemsfor future local reuse. - Item autocomplete API mode:
Home/ItemNameSearchnow uses ESI inventory-type search (strict=false) as the primary suggestion source, resolves type IDs to names, and caches both prefix suggestions and newly discoveredItemsrows locally for faster follow-up lookups. - Item autocomplete ore-variant completion:
Home/ItemNameSearchnow adds targeted grade probes (I/II/III-Grade) and always merges localOresprefix/contains matches, so ore variants likeScordite III-Gradeand compressed ore names remain visible in suggestions.
Fixes
- Price Control partial-reload handler stacking: calculator list click binding now uses namespaced
off(...).on(...)rebinding in_PriceCreator, preventing duplicate click handlers after repeated partial reloads. - Price Rules save lookup fallback:
AddPriceRulenow falls back to ESI inventory-type search when a typed item is missing from localItems, then caches the resolved item locally before saving so missing ore grade entries can still be added.
Fixes
- Startup reliability hardening (500.30): startup now avoids DB-dependent MySQL version auto-detect by default and runs migration/schema initialization inside a configurable retry loop, preventing transient post-reboot DB readiness delays from immediately crashing app boot.
- Mining op join infinite-loading fix: hardened dashboard mining-op refresh retries so the loading overlay is only applied once across retry cycles (preventing stacked overlays), plus added request timeouts and clearer retry-exhausted feedback when an active-op refresh cannot be loaded.
- Mining op sync latency reduction: scheduler mining-op updates now cache mining-ledger API responses per linked character for the full sync cycle (so repeated linked accounts are not re-fetched) and use a fail-fast 10-second ledger request timeout, reducing multi-minute syncing stalls when ESI calls hang.
- Mining op ledger timeout alignment: removed mining-op ledger dependency on the default 30-second HTTP timeout by applying the mining-op timeout override across all mining-op ledger update fetch paths (scheduler cached fetch, initial Start Ledger pull, and update fallback fetch), preventing 30-second-per-call waits during op sync.
- Mining op API caching removal: removed in-process mining-op ledger response caching from the op update loop, so mining-op sync now relies on ESI’s own cache behavior instead of app-side response reuse.
- Mining op timeout removal: mining-op ledger update fetches now use no app-side HTTP timeout cap (`Timeout.InfiniteTimeSpan`) so op sync calls are no longer cut off by local timeout limits.
- Mining-op history filtering fix: buyback submissions (`OpType = Personal`) are now excluded from mining-op history/totals/personal history query paths, so buyback orders no longer show up in mining operation history pages.
- Mining-op history separation flag: added persisted
IsBuybackOrderon mining ops (with migration backfill for existing Personal rows), set the flag when buyback orders are created, and updated mining history/totals filters to exclude flagged buyback rows so future buyback submissions stay in buyback flows instead of mining-op history pages. - Price Rules save fix (Price Control): added a page-scoped anti-forgery token for the Price Rules tab and now post that token in both header and form data on Add/Delete rule actions, fixing generic
Error saving rule.failures when adding price controls.
Changes
- User guide expansion (Price Controls): added a screenshot-first Price Controls walkthrough to
USER_GUIDE.mdwith captured UI references for formula setup, step configuration, pricing-system mapping, and ore-value validation, plus new screenshot assets underdocs/screenshots/price-controls/. - In-app guide conversion: replaced the old video-links-only
Tutorialspage with a full on-site HTML guide section that documents major site modules and embeds Price Controls screenshots served fromwwwroot/images/guides/price-controls/.
Fixes
- Lucky Slot Machine 400 fix: slot-machine save/spin POSTs now use a page-scoped anti-forgery token source and include
__RequestVerificationTokenin form data (as well as headers), preventing cross-form token mismatches that could return400 Bad RequestonAddSlotMachineCodes/SpinSlotMachine.
Fixes
- Setup Wizard scope save 400 fix: Step 1 now always includes the anti-forgery token in form POST data and posts
selectedScopeswith traditional array serialization, andSaveSetupWizardScopesnow binds from a nullable form list for more reliable scope payload handling. - Setup Wizard all-step anti-forgery fix: wizard saves/completion now use a dedicated wizard-scoped anti-forgery token source instead of a global token lookup, preventing cross-form token mismatches that could cause every wizard POST to return
400 Bad Request. - Setup Wizard completion 400 fix: final
/Setup/CompleteSetupWizardnow posts anti-forgery token in form data as well as headers (matching step-save behavior), preventing header-only token validation failures on finish. - Setup Wizard Step 4 save speed-up: refining save now uses bulk mapping loads and in-memory updates (instead of per-ore mapping queries), projects only required ore fields, removes an extra intermediate save, and precomputes refine multipliers to cut Step 4 save latency.
- Setup Wizard Step 4 speed-up follow-up: added short-lived ESI ore-skill caching for per-ore mode, skip full mapping recompute when refining inputs are unchanged (while still healing missing mappings), and reduced change-tracker overhead during mapping updates.
Fixes
- Discord setup save reliability: corrected Discord role/channel sync cleanup in
DiscordController.DiscordSetup()to stay scoped to the activeDiscordServerId, fixed a role-prune loop issue, and added a safe fallback to render persisted DB values when live guild sync is unavailable. - Discord settings endpoint hardening:
DiscordController.SaveRoleis now explicitPOSTwith anti-forgery validation, and the Discord settings form now posts with an anti-forgery token. - Setup Wizard Discord channel save reliability: switched Discord channel ID fields to precision-safe text entry and added server-side trim/validation/parsing in
SaveSetupWizardDiscordso large Discord snowflake IDs save reliably. - Setup Wizard save hardening: pricing/refining/loyalty save endpoints now reject invalid model binding states, refining implant values are clamped to the valid
0.00-1.00range, and pricing step save now returns a clear error when no pricing formulas exist yet. - Recruitment Welcome save toast fix: welcome-page save now uses app-safe toast helpers (prefers
toastr8, falls back totoastr) so save callbacks no longer throwReferenceError: toastr is not defined. - Bulletins editor save toast fix: bulletin save now emits explicit success/error toasts (prefers
toastr8, falls back totoastr) soSave Bulletingives clear confirmation/error feedback. - Recruitment setup save toast fix: recruitment setup save now uses app-safe toast helpers (prefers
toastr8, falls back totoastr) so save callbacks no longer throwReferenceError: toastr is not defined. - Setup + Setup Wizard toast compatibility fix: setup-page and setup-wizard saves now use shared safe toast helpers (prefers
toastr8, falls back totoastr) so callbacks do not fail when only one toast global is available. - Discord setup save UX fix: Discord settings now save through AJAX with in-page toast feedback, so
Save Settingsno longer navigates to raw JSON at/Discord/SaveRole. - Discord setup save responsiveness: Discord settings save now shows a clear loading state (
Saving...+ spinner overlay) and server persistence now applies only changed notification rows instead of delete/reinsert each submit. - Discord Additional Corp Auth button fix: updated the
Add Corp To Discordaction to use scoped Discord button styling with a minimum width, preventing label clipping/truncation.
New Features
- Planetary Industry: new module showing all colonies across your main and alt characters in one view. Each planet card displays its status (Active / Expiring / Expired / Idle), extractor countdown timers, upgrade level, and pin-type chips (extractors, basic/advanced IFs, launchpads, etc.). Colony data is cached in the database and refreshed by the background worker every 30 minutes, with an immediate live ESI fetch on first visit.
- PI scope gate: if the
esi-planets.read_customs_offices.v1ESI scope is missing, the Planetary Industry page shows a clear prompt and a one-click re-authorisation link instead of an error.
Fixes
- PI refresh timing: Planetary Industry page loads now force an immediate live ESI refresh for the requesting character (no 30-minute wait), and also refresh linked characters when their PI cache is stale or missing.
- PI view crash fix: removed the duplicate-key assumption in the Planetary Industry aggregation view so multiple characters with colonies on the same planet no longer throw rendering exceptions.
- PI status timer fix: corrected extractor expiry rollup logic so colonies with extractor pins but no expiry timestamp no longer show false
Expired/0001-01-01-style next-timer output. - PI cache cleanup: background PI sync now removes deleted in-game colonies (and their cached pins) from local cache tables, preventing ghost colonies and stale alert state from lingering after refresh cycles.
- Legacy planets fallback safety: hardened the older
Home/GetPlanetspath with safe default PI objects when ESI calls fail so it no longer null-refs if that fallback UI is used.
Security Hardening
- Anti-forgery enforcement: added
[HttpPost]+[ValidateAntiForgeryToken]protection to milestone, slot-machine, gateway, and recruitment setup save endpoints; slot-machinefetchPOST calls now include the anti-forgery header. - Recruitment DOM-XSS fix: replaced unsafe HTML-string error rendering in
NewApplicationwith escaped/safe error box rendering before DOM injection. - Corporation lookup safety: removed raw corporation description rendering and now only outputs clickable corp URLs when they are valid absolute
http/httpslinks. - Claude API endpoint hardening: added constant-time API-key comparison, HTTPS/loopback transport checks, and optional caller IP allowlisting via
AppSettings:ClaudeApiAllowedIps. - Header + script hardening: added baseline security headers (including CSP report-only) and switched protocol-relative third-party script loading to explicit
https://.
Highlights
- Personal Mining History mockup: added a new standalone UI concept file,
eve-personal-mining-history-mockup-a.html, forHistory/PersonalMiningHistorywith EVE HR-style navigation, KPI strip, filter controls, and expandable operation rows. - Recruitment mail tab refresh: redesigned the application-view mail panel to use scoped, modern EVE HR styling (toolbar stats, cleaner row hierarchy, and responsive recipient/message split), and switched lazy-load selectors to the scoped
.js-mail-rowclass to avoid legacyMail/Read/UnReadstyle and click-handler collisions. - Milestone delete flow: added a new delete action on
Milestone Setupcards with confirmation and a matching server endpoint so Loyalty Setup admins can fully remove milestones (including associated progress rows), not just disable them. - Recruitment tab consistency pass: refreshed
JournalandNotificationstab panels to align with the updated mail-tab styling (scoped panel shell, compact summary toolbar, cleaner row spacing, and responsive behavior), and switched notification read-state styling to scopedis-read/is-unreadclasses to prevent legacy style bleed. - Setup Wizard context pass: added inline context/help text across wizard selections (scopes, system toggles, pricing mappings, refining options, loyalty controls, recruitment fields, and Discord channels/messages) so each option clearly explains behavior and impact before saving.
- UI/button collision hardening: replaced broad shared click handlers with scoped namespaced bindings across alliance, fleet, logistics, and mining-op setup panels to stop cross-page double-fires after partial refreshes.
- Duplicate toast reduction: disabled legacy global AJAX toast emission paths and tightened toast dedupe/rate-limiting so repeated button actions stop stacking success/error popups.
- System Settings save follow-up: moved
/Setup/SystemSettingssave to a single explicitfetch(...)path with in-flight locking to prevent a second hidden/globalSavedtoast from appearing on save. - Setup save follow-up: moved the main
/Setup/Setupform save (#SetupForm) to a namespaced, in-flight-guardedfetch(...)submit path so setup saves no longer emit duplicate success toasts after partial reloads. - High-risk duplicate toast cleanup: added namespaced event rebinding (plus in-flight guards where needed) for Fleet Up settings, Logistics settings, Setup User Management actions, Order submit/confirm/cancel actions, SRP action handlers, and Setup price-rules add/delete actions to stop stacked toasts after partial refreshes.
- Security hardening: stopped global exception stack-trace leakage, enforced anti-forgery on key state-changing endpoints (Home/Setup/Alliance/Bulletins/Recruitment), added shared anti-forgery token/header wiring in layout + core jQuery helpers, sanitized New Member Guide body/title save paths, and removed raw bulletin-title rendering to close stored-XSS vectors.
- Dynamic UI stability pass: replaced deprecated DOM mutation hooks with
MutationObserver-based handling and removed duplicate script/UI initialization paths that could rebind controls. - Layout/ID cleanup: fixed duplicate IDs in key partials and corrected trailing layout tag placement so dynamic partial rendering no longer destabilizes selectors or script execution order.
New Features
- Custom milestones: added a corp-scoped milestone system for
Ore Mined,Ships Killed, andBounty ISKwith persisted definitions, progress, completions, and event tracking. - Milestones member page: added
/Home/Milestonesso members can see active milestone progress, completion state, repeat counts, rewards, and earned trophies. - Milestone setup: added
/Home/MilestoneSetup(Loyalty Setup permission) with milestone creation, metric/window/filter controls, reward options, and enable/disable actions. - Random trophy icon selection: added randomized icon-pack generation with selectable milestone trophy icons (glyph + gradient + ring), persisted on each milestone.
Changed
- Automatic milestone progression: mining-ledger deltas, killmail attacker processing, and bounty ingestion now emit milestone events so milestones progress from existing corp activity without manual updates.
- Corp Members trophies: corp roster rows now display earned milestone trophy icons per member with hover tooltips showing milestone titles.
- Top-banner trophies: logged-in users now see earned milestone trophies in the top banner/nav user info (desktop and mobile), including repeat-completion counters.
- Loyalty navigation: added dashboard entries for
MilestonesandMilestone Setup.
Highlights
- Killmail processing hardening: fixed killmail LP award paths for null Discord channels, safer
awox/soloboolean coercion, and improved corp cache parsing resilience. - Killmail/bounty pipeline controls: added configurable zKill queue IDs, intake backpressure limits, and startup/task flow updates to improve queue stability.
- Setup Wizard polish + routing: improved first-login setup wizard copy/layout and fixed dashboard routing/guard behavior so eligible users are redirected cleanly until corp setup completion.
- Setup Wizard completion model: added persisted corp completion state with completion endpoint wiring and post-wizard return routing.
Bug Fixes
- UI alignment pass (Fleet/SRP/Fitting/Setup): fixed multiple input-and-button alignment issues across
SRP/SRP,Fleet/FleetTypes,Fleet/FleetHistory,Home/FittingHome,Home/MiningOpsDownload, andSetup/DirectorOnlyPageby replacing legacy inline layouts with responsive grid/flex control rows and consistent baseline alignment. - Button text overflow: updated affected pages to use responsive button sizing (
width: autowith sensible minimums and mobile full-width fallbacks) so labels no longer spill outside button boundaries. - Fitting import upload row: modernized
Views/Fitting/ReadFile.cshtmlfrom a legacy inline div-button row to a semantic file-input + button layout, including mobile stacking and a stable upload target lookup for the existing AJAX import flow. - Setup pricing-system assignment save: fixed the Save buttons in
Assign Calculators to Pricing Systemsfailing when no Market Hub dropdown is rendered in the row. The client now posts a safe default hub (Jita) when the control is missing, andSetupController.SetPriceTypenow accepts nullable market-hub input and defaults server-side toJita. - Setup pricing-system dropdown preload: fixed saved calculator selections not reliably showing on page load when duplicate
PricingTypeMappingsrows existed.SetPricesnow loads the newest mapping per pricing system,SetPriceTypeupdates the newest row and removes older duplicates, and setup bootstrap no longer creates repeatedPricingSystem.Nonerows. - Setup pricing-system save authorization/binding: aligned
SetPriceTypeauthorization with the Prices page (BuybackSetup), switched assignment-rowdata-idvalues to numeric enums, and enforced JSON parsing for the Save AJAX response. This prevents false-positive saves on auth redirects and makesSale/Logistics/SRPenum binding deterministic. - Setup pricing-system dropdown selected state: fixed assignment dropdowns rendering the first/default calculator on reload by replacing complex dictionary-object dropdown binding with explicit
<select>option rendering keyed to each system’s savedPricingTypeId. Saved selections now render correctly per system when the page loads. - Mining Prices refresh + mineral-pricing toggle: fixed
Home/GetPricePerM3values appearing stuck after changing pricing controls or toggling Mineral Pricing. The page now respectsSystemSettings.MineralPricing(direct ore sale pricing when off, refined-mineral valuation when on), uses mode-specific cache keys, and setup/pricing save endpoints now invalidate Mining Prices cache immediately after save. - Ore pricing formula hover details: added per-ore hover formulas with real numbers on both
Mining Pricesand liveMining Opore breakdown rows. Tooltips now show how each ore value was computed (including refined-mineral component math when mineral pricing is enabled, or direct unit-price-per-m3 math when it is disabled). - Ore valuation formula consistency: corrected ore refined-value math so
Mining Prices, ore hover formulas, and live mining-op ore totals all use the same per-ore component equation (floor(refine amount × refine %) × mineral price / amount to refine, then/ volumefor m3 views). Also updated mining-ledger refresh paths to recalculate stored ore unit price on sync so ore values stay aligned with current pricing settings. - Mining Op end-operation UX for all members: ending an op now sets a shared transient ending state so everyone currently in that op sees an in-panel
Ending Operation...spinner overlay during live polling (not just the user who clicked End). End buttons are also locked while the request is in flight, and the client fast-polls op state until completion. - Mining Op end-operation background processing + immediate removal:
End Opnow marks the op inactive immediately, returns a clear background-processing message, and pushes a SignalRupdateFinishOprefresh right away so the live op panel disappears for all members and becomes non-interactable while final ledger refresh/payout work continues in a background task. Confirmation availability now follows when that background finalization completes. - Mining Op pricing mode enforcement: mining-op value panels now re-resolve live ledger ore prices through the sale pricing workflow on each refresh (banner totals, ore breakdown, and member payout panel). With
SystemSettings.MineralPricing=true, mining ops now always value ore using refined-mineral pricing instead of stale/raw ore market pricing. - Mining Op create-flow spinner continuity: fixed the create-op transition so the loading spinner now remains visible until the joined active-op panel is actually returned. Added active-op-aware retry behavior to
GetJoinedOp()and switched create success from immediateUpdatePage()to spinner-backed active-op polling, eliminating the briefNo active mining operations right now.flash. - Mining Op create/join transition loading gap: extended active-op retry windows for create/join flows so spinner states stay active while backend join/start processing completes, and updated initial
/Mining/MiningOpspage load to use spinner-backedGetJoinedOp(...)with active-op retry when the server detects the user is already in an active op. - Mining Op scheduler latency reduction: reduced backend sync jitter by moving mining-op scheduler checks from roughly 1-minute cadence to a 10-second due-op cadence (with a 5-second worker heartbeat), and now querying only due ops (
NextUpdate <= now) for sync processing. This narrows the countdown-to-actual-sync gap. - Mining Op member panel flicker: fixed miner contribution cards briefly disappearing/reappearing during live op polling by carrying forward the previous members HTML until the fresh members partial returns.
- Mining Op mineral-pricing parity + indicator: when mineral pricing is enabled, op-level gross/to-miners/tax values now align to the Refined Values total, member payouts are scaled to the same refined gross basis, and the banner now shows a
Mineral Pricingmode chip so valuation mode is explicit. - Mining Op push refresh via SignalR: replaced mining-op sync refresh dependence on countdown fast-polling with server push updates. Added an ASP.NET Core SignalR client on the dashboard, broadcast mining-op update events (sync/join/leave/end/create/manual refresh/auto-end), and changed countdown fast-poll to fallback-only when SignalR is disconnected.
- Mining Op inactivity auto-end (never-active ops): updated scheduler inactivity logic so ops are auto-ended after 1 hour with no activity even if they never had a successful sync (
LastLedgerUpdateis null). The inactivity clock now usesLastActivityTime, thenLastLedgerUpdate, thenOpStartTime. - Mining Op per-character ore attribution tab: added a new
My Orestab in the live op centre panel that shows the current viewer’s mined ores split by mining character (main + linked alts), including per-character totals and per-ore value rows. AddedMiningLedgerItems.SourceUserId(migration20260315000005_20260315_MiningLedgerSourceUserAttribution) and updated ledger start/sync writes to store source-character attribution for new ops, while keeping legacy ops compatible with an attribution note. - Mining Op member payout panel flicker/blanking: hardened the member refresh path to prevent overlapping
GetMiningOpMemberscalls from clobbering the panel during rapid polling/push refresh cycles. Member updates are now serialized with queued coalescing, non-HTML/JSON responses no longer replace member HTML, andUpdatePage()no longer triggers a redundant second member fetch on top ofGetJoinedOp(). - Mining Op sync-delivery timing gap: fixed the post-countdown delay where the timer could restart a new cycle before refreshed ledger values appeared. The mining-op banner now carries a machine-readable
last synctoken, due-sync polling now waits for both a futureNextUpdateand a changedlast syncmarker before declaring the cycle refreshed, andGetJoinedOp()calls are serialized with queueing so overlapping refreshes cannot race and present stale data as a fresh cycle. - User Management load performance: sped up
/Home/UserManagementinitial render by returning a lightweight shell and loading the character section asynchronously through/UserControls/GetCharacters. Also reduced DB overhead in both actions by replacing heavyGetUser(...)paths with directAsNoTrackinglookups, avoiding unnecessaryAltsnavigation loading on first paint. - Mining Ledger load performance: optimized
/Mining/GetMiningLedgerwith short-lived ESI ledger caching per character, a single-pass aggregation pipeline, direct invoice-item projection via join query, and controller-side precomputation of ore names/prices/totals._Ledger.cshtmlnow renders precomputed values instead of callingGetPrice/GetNameper row, and theGet Ledgerbutton now blocks repeat clicks while requests are in flight. - Mining Ledger duplicate key crash: fixed
An item with the same key has already been addedon/Mining/GetMiningLedgerby making item-name dictionary creation duplicate-safe. Item names are now grouped bytype_idbefore dictionary materialization, so corp datasets with duplicateItems.type_idrows no longer throw. - Mining Ledger and invoice pricing batch lookup: replaced per-item sale-price calls in
MiningControllerwith batchedGetSalePrices(...)lookups for both ledger render and invoice creation paths, reducing repeated pricing pipeline setup/queries per row and improving responsiveness for larger ledgers. - Order Confirmations Block action: fixed the
Blockbutton click handler throwingReferenceError: e is not definedinDashboard.js, which could break confirmations-page interactions. The handler now correctly receives the event parameter, unblocks UI on errors, and avoids duplicate cancel-dialog invocation. - Order Confirmations save failure: fixed payout confirmation writes that could fail with EF's generic
An error occurred while saving the entity changesmessage by ensuringPayoutHistory.UserIdis always populated fromMiningOpMember.UserId(even when theUsernavigation is not loaded) and by hardening fleet payout math against missing fleet setup / zero tick totals that could produce invalid payout values. - Order Confirmations loyalty points award: fixed confirmed ops not awarding loyalty points from miner tax amounts.
ConfirmOp/ConfirmAllOpnow call the context-awareTallyPointsoverload so point updates are tracked in the same request context and persisted with confirmation saves. Also hardened standaloneCoreWorkflowService.TallyPoints(...)to save changes when it creates its own context. - Task scheduler mining-op speed pass: optimized
TaskProcessor.Schedulerwith a fast due-op ID query to skip heavy include-graph loading when no ops are due, switched due-op loading to split query, resolved payout workflow scope once per scheduler run instead of once per op, and persisted allNextUpdateanchors in oneSaveChanges()before sync/payout work. Also reducedAutoEndInactiveOpsscan cost by prefiltering to active ops older than 1 hour and reusing a single UTC timestamp in-loop. - Buyback refining calculator settings: replaced the old base-yield-only refine setup with calculator-style settings on
Setup/Setup(Structure, Rig, Security Status, Reprocessing Skill, Reprocessing Efficiency, Ore Processing Skill, Implant), persisted those values inOreRefiningPercentages, and switched refine-percentage generation to the EVE-style equation so saved setup values now directly driveOreRefiningMappingsused by mineral pricing. - Buyback ore-processing mode choice: added a corp-selectable
Ore Processing Modeso each corp can choose eitherGlobal LevelorPer Ore (ESI). Refine mapping generation now follows that selected mode and persists it inOreRefiningPercentages.
New Features
- Mining Op — live next sync countdown: replaced the static "Next sync 9:15 AM" text in the op meta bar and Sync Health card with a live client-side countdown (
5m 42s → syncing...) so users always see exactly how long until the next ledger refresh ticks over. - Mining Op — refined values tab: added a new
Refined Valuestab beside Ore Breakdown/leaderboards that calculates refined outputs using the configured setup refining percentages and ore-to-mineral mappings. The tab now combines all refined outputs into a single per-mineral summary (amount and ISK value per mineral), shows a combined refined total, and includes a clear hint when some ores are missing refine setup mappings.
Bug Fixes
- Mining Op invite links — auth redirect and join reliability: fixed the
MiningOp/{code}invite OAuth callback flow so successful auth returns users to the invite route, repeat clicks on the same invite no longer fail, and users already active in a different op still get the correct "leave current op" guard. Also hardened callback corp/op checks to avoid null failures during invite auth. - Mining Op join button — spam click protection and loading UX: hardened the join click path so repeated clicks no longer fire duplicate join requests. Join buttons now lock while the request is in flight, show an inline
Joining...spinner state, and then load the joined op panel via a spinner-backed refresh so users see a clear loading transition into the op screen. - Mining Op refined minerals — zero ISK values: fixed the new refined-values tab showing
0.00 ISKfor minerals by switching valuation to the same live sale-pricing workflow used by ore pricing (GetSalePriceswith shortcut fallback), instead of relying only on directMineralPricestable reads. - Mining Op payout consistency and ISK/hr: fixed member-side ISK/hr and payout display drift by normalizing member start times with UTC-safe conversion (so
DateTimeKind.Unspecifiedvalues no longer inflate rates) and by calculating member estimated payout from the same live ledger + tax/fleet formula used by the op banner, keepingTo Minersand pilot payout aligned for single-pilot ops. - Mining Op tab persistence during polling: fixed automatic op refresh from constantly snapping the centre panel back to
Ore Breakdown. The selected tab is now remembered and restored after eachGetJoinedOp()partial refresh so users can stay onRefined Values,Top Miners, orISK / hrwhile polling continues. - Ore names — startup auto-refresh from ESI: added startup synchronization that fetches each ore
Type_Idfrom ESI and updatesOres.OreNameautomatically, so CCP ore renames no longer require manual mapping maintenance. - Ore name startup sync control: added
AppSettings:EnableStartupOreNameRefreshso this startup refresh only runs when explicitly enabled in configuration (defaultfalse). - Mineral mapping normalization: added migration
20260314_NormalizeMineralOreMappingMineralIdsto normalizeMineralOreMappings.MineralIdfrom legacy EVE type IDs to trueMinerals.MineralIdforeign keys. Updated ore pricing paths to resolve market type IDs throughMinerals.EveCentralId(with legacy fallback) so Veldspar and other ore mineral lookups remain correct. - Mining Op — next sync time accuracy:
NextUpdatewas previously stamped after the sync completed, so the displayed time reflected "10 min from when processing finished" rather than from when it started. The scheduler now savesNextUpdatebefore callingTryUpdateOp, so the correct upcoming time is visible immediately — even while a slow multi-member sync is still running.
Bug Fixes (earlier today)
- Fleet settings save button — full-page navigation: fixed "Save Fleet Settings" navigating to a raw
/Fleet/GetSettingspage instead of saving in-place.Html.BeginFormwas generating the wrong action URL when the partial was loaded via AJAX, and the submit handler was unreliable inside injected partial content. ReplacedHtml.BeginFormwith an explicit<form action="/Fleet/SaveSettings">tag, added[HttpPost]toSaveSettings, and moved the AJAX submit handler intoFleets.cshtmlwhere it registers reliably on page load. - Mining Op payout donut — wrong empty-state values: fixed the payout split graph showing hardcoded 75% / 10% / 0% before any ore is mined. The graph now reflects the op's actual configured corp tax rate and fleet percentage from the first render. The Fleet % Apply button now also refreshes the panel immediately via
GetJoinedOp()so the donut updates without a manual reload. - Mining Op stale alert — false positives: fixed "Mining ledger attention required" firing incorrectly after every sync cycle. The root cause was treating
APIExpirationas a staleness indicator — butUpdateOpresets it toDateTime.MinValuebefore each cycle andMergeLedgerExpirationonly sets it back on a successful API call, so the ESI response cache (~5–10 min) was expiring between cycles and triggering false warnings. Members are now only flagged as stale when the op has synced at least once but a member'sAPIExpirationis still the reset default, which means their specific API call failed. Also tightened the "no sync" banner so it only fires after the op has been running for more than 30 minutes without a successful sync.
New Features
- Mining Op ISK/hr: added a live ISK/hr KPI to the op banner (gross op value ÷ op duration), visible once ore data exists. Added per-member ISK/hr below each miner's estimated payout in the contributions panel, calculated from each member's individual join time.
- Mining Op auto-end on inactivity: ops now automatically close after 1 hour with no new mining activity. Added
LastActivityTimetoMiningOp(migration20260314_MiningOpActivity), updatedLedgerProcessorto stamp it whenever ore quantities increase during a sync, and added an inactivity check toTaskProcessor.Schedulerthat ends the op when no activity is detected for ≥ 1 hour on an op that has been running ≥ 1 hour and has had at least one successful sync. - Mining Op auto-name: if a user creates an op without entering a name, the op is automatically named
CorporationName - UserName - MM/dd/yyyy. Public ops (no corp context) useUserName - MM/dd/yyyy. - Mining Op leaderboards: added two leaderboards to the ore breakdown panel — Most Ore Mined (raw ISK value contributed per pilot) and Highest ISK/hr (after-tax payout ÷ time in op). Both match the LP Top Point Earners style: gold/silver/bronze rank colouring, portrait avatars, and relative progress bars. Hidden until after the first sync.
- Mining Op sync ring: replaced the plain "Next sync" text in the Sync Health card with an SVG circular countdown ring. The arc drains as the cycle runs down, turns amber in the final 25%, and glows. Countdown text and "next sync" label sit in the centre of the ring.
Bug Fixes
- Mining Op — miner payout wrong value:
LedgerPayoutCalculatorwas multiplying each member's raw ore value byTaxRate / 100instead of1 - TaxRate / 100. With a 10% tax rate, miners were receiving 10% of the value (the tax amount) instead of 90%. Fixed. - Mining Op — sync refresh gap: after the countdown hit zero there was no automatic data refresh.
Dashboard.jsnow fast-pollsGetJoinedOpevery 3 seconds once the countdown expires and stops as soon as new data arrives. - Mining Op — sync cycle UX gap: changed the sync schedule from 10 to 11 minutes so the backend almost always fires before the ring fully empties, preventing the ring from sitting at zero while users wait.
- Mining Op — duplicate ISK label: the Total Op Value strip was showing "1,350 ISK ISK" because
ToIsk()already appends "ISK" and the template had a hardcoded suffix. Removed the duplicate.
Bug Fixes
- Leaderboard — gifted users excluded: fixed the Top Point Earners leaderboard showing no entries despite corp points existing. The leaderboard was filtering users by
CorporationId, which excluded users who received gifted points from outside the corp. BothGetTopPointEarners()andTopPointEarners()now look up users by their actual UserPoint user IDs so gifted members appear correctly. - LP Store — item icons: fixed store items always showing a generic box icon. Added a
LookupItemTypeIdendpoint that resolves EVE type IDs by exact item name against the local item database, added a live icon preview in the LP store setup form that updates as you type, and saves the resolved type ID so the store now shows real EVE item icons for in-game items. - Settings forms — full-page navigation: fixed Corp Tax Rates, Logistics Settings, System Settings, and Fleet Up API Settings save buttons navigating away to raw JSON instead of saving in-place. All four forms now submit via AJAX and display a success or error toast notification.
- Refining skills — silent failure on scope redirect: fixed the "Set My Skills for Mineral Refining" button silently doing nothing when the user lacked the
esi-skills.read_skills.v1scope. A case mismatch (data.Urlvsdata.url) was preventing the re-auth redirect from ever firing. Added success and error toast feedback so users know when refining skills have been updated or a server error has occurred.
Changes
- Improved the Patch Notes page readability with a dedicated page shell, clearer typography, collapsible date cards, and per-section expand controls for long update lists.
- Cleaned up another warning-reduction batch by tightening OAuth state-token nullability, fixing compact controller warning clusters in
BulletinsController,BugReportsController,AllianceController,FittingController,FleetController, andHistoryController, and clearing smaller framework-noise warnings inLookupController,RecruitmentController, andNavigationViewComponent. - Continued the DbContext refactor by adding a DI-backed
ProcessServicesbundle, registeringTaskProcessorthrough DI, and moving the hosted task processor plus the manualRefreshUsersflow off directAppSettings.CreateContext()andnew TaskProcessor()construction. - Moved
CoreProcess,LedgerProcessor, andPayoutProcessdefault-context creation onto the sharedBaseProcess.CreateContext()path, updatedCoreProcess.GetSkills()to use that central helper, and removed the last active no-contextnew CoreProcess()path fromHomeController.UpdateSkills. - Moved
BugReportsControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from the bug report index, submit, detail, reply, admin, status-update, and attachment flows. - Moved
OrderControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from order submit, current-order, fulfillment, confirm, and cancel flows while keeping the existing per-requestOrderProcessor(_Context)path. - Moved
HistoryControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from personal history, corp profits, op history, bounty history, mining-op totals, and personal mining-history detail flows. - Moved
FleetControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from fleet auth checks, active/current fleet views, pending/confirm/reject flows, settings/details/history search, and fleet-type management. - Moved
MiningControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from mining ledger, invoices, payment matching, top-miner, mining-op join/share/public-buyback, and fleet-settings flows. - Moved
UserControlsControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from mail, blacklist search, character/alt management, scopes, daily graph data, loyalty-point merge, and wallet-journal flows. - Removed the remaining
AccountControllercalls to the staticAppSettings.CreateContext()bridge and routed the add-alt, dev-login, item bootstrap, callback, and logoff cleanup flows through the already-injectedIDbContextFactory<EHRContext>instead. - Updated Lucky Slot Machine status handling so the page now shows the real disabled reason on load instead of always ending on
Ready. Press spin.when daily limits or empty prize inventory have already disabled the button. - Moved
OrderProcessorandUpdateRefiningProcessonto the sharedBaseProcesspattern, removed their remaining directAppSettings.CreateContext()constructors, and registered both process types in DI for the next controller-service migration slices. - Moved
BulletinsControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from bulletin list, load, create, save, and delete flows. - Moved
StoreControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from store group, item, item-part, and setup-list flows. - Moved
StructureControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from structure scope, access-check, info, settings, and save-settings flows. - Moved
SRPControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from SRP history, payout, submit, approval, killmail, and form-management flows. - Moved
LookupControlleroff the remaining staticAppSettings.CreateContext()bridge path and onto injectedIDbContextFactory<EHRContext>for its authenticated character-contact lookup flow. - Moved
FittingControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from fitting-checker, import, purge, and fitting-viewer flows. - Moved
AllianceControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from alliance setup, invite, join, approval, member, banner, and executor-switch flows. - Moved
LogisticsControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from logistics orders, permissions, volume-pricing, and fulfillment flows. - Moved
DiscordControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from Discord auth, server setup, alerts, title mapping, and corp/alliance auth flows. - Started a bounded
SetupControllerconversion by moving the setup dashboard, Patreon benefits, Patreon validation, director-scope option helper, and refining-percentage flows off the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>. - Extended the bounded
SetupControllerconversion through pricing and user-management, moving the set-prices, price refresh, permission, approve/delete user, and setup-save flows off the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>. - Extended the bounded
SetupControllerconversion through pricing-type builders and price overrides, moving calculator-type, formula-row, and override-management flows off the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>. - Finished the remaining
SetupControllerbridge cleanup by moving reset, custom links, permission editor, system settings, scope setup, director-only, and notification-alt flows off the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>. - Moved
RecruitmentControlleroff the staticAppSettings.CreateContext()bridge and onto injectedIDbContextFactory<EHRContext>, removing controller-local ambient context creation from recruitment login, corp application, application review, mail/history, form builder, notes, and related management flows. - Started a bounded
HomeControllerconversion by injectingIDbContextFactory<EHRContext>and moving the corp header, user management, vacation toggle, and scheduled-task management flows off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through the corp-members payload path, moving the DB-backedGetCorpMembersExternal,GetCorpMembersJson, andResolveCorpNamescontext creation off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through corp-member alt/search, mining-op, payout, haul submit/preview, pending-order, op-detail/end, laser-tracking, and ore-price-per-m3 flows, moving that mining/order block off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through the mining-op lifecycle block, moving join, leave, start, refresh-ledger, mining-op-member, and corp-ledger-total flows off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through the order-confirmation, price-adjustment, top-miner, and loyalty-setup block, moving confirm/cancel/validate, blocked-user, price-adjustment, point-setup, and loyalty-item management flows off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through point-history, bulletin/store, slot-machine, notification/gifting, fitting, calendar, and FleetUp setup flows, moving those controller and helper paths off the staticAppSettings.CreateContext()bridge. - Extended the bounded
HomeControllerconversion through panic-message management, navigation info, alt-removal, skills/planets, calendar-check, intel parse, and member-dropdown flows, moving that lower-middle block off the staticAppSettings.CreateContext()bridge. - Finished the remaining
HomeControllercontroller-action bridge cleanup by moving monthly-income, CSV download, item-search, ad-blocker, error-log, store-list, and ESI-audit flows off the staticAppSettings.CreateContext()bridge. - Centralized the remaining static
DropDownsandShortCutscontext access behind localCreateContext()helpers, removing the last scattered directAppSettings.CreateContext()call sites fromHomeController.cswhile leaving those static helper classes queued for a later service extraction. - Moved the embedded
DropDowns,ShortCuts, andHelperClassutility types out ofHomeController.csand intoHomeControllerUtilities.cs, reducing controller file sprawl without changing the existing helper call sites yet. - Added an injected
IShortcutService/ShortcutServicepath, registered it in DI, and moved the current controller-sideShortCutsusages inHomeController,MiningController,SetupController,FleetController,FittingController, andSRPControlleroff the static helper while leaving the existing Razor, helper, and process callers for a later slice. - Added
IOrderWorkflowService/OrderWorkflowService, moved theOrderControllersubmit/current/fulfillment/confirm/cancel business logic into that service, and added an explicit-contextOrderProcessor.ProcessesNewOrder(...)path so the order flow no longer needs controller-ownedOrderProcessorconstruction. - Added
ILogisticsOrderWorkflowService/LogisticsOrderWorkflowService, madeParseItemsProcessinjectable throughProcessServices, and moved the active logistics place/update order workflow out ofLogisticsControllerso that controller no longer ownsPricingProcessorParseItemsProcessconstruction for those paths. - Added
ICoreWorkflowService/CoreWorkflowService, registered it in DI, and moved the activeHomeControllernotification, loyalty-point tally, and skill-refresh paths off directnew CoreProcess(_Context)construction while leaving the payout-heavy mining-operation flows for a later slice. - Moved the shared principal/auth snapshot helpers in
Extensions/UserExtensions.csonto centralizedCreateContext()bridge points, changedGetUserId()to prefer the existing auth claims, pushedAllianceSwitchandDirectorinto the cached auth snapshot, and removed the per-request layout lookup for alliance mode by servingGetAllianceSwitch()from the snapshot cache with a DB fallback only when claims are incomplete. - Removed the remaining constructor-time
AppSettings.CreateContext()calls fromModels/HomeViewModels.cs, changed the mining-op start and ore-entry models into pure data containers, and moved mining-op start-form plus ore select-list population intoHomeControllerso the_MiningOpspartial no longer constructs a DB-backed view model from Razor. - Removed the dead constructor-time
AppSettings.CreateContext()load fromModels/SetupViewModels.cssoSetupViewModelis now a pure container, and changedHelpers/JsonErrorResult.csto use claim-backed corp and user IDs for error logging with a centralizedCreateContext()fallback instead of always opening a context just to resolve the current user first. - Added context-aware
AuditLog.AddLog(...),UserPoint.IncreasePoints(...), andUserPoint.SpendPoints(...)overloads inModels/Models.cs, switched the activeCoreProcess,TaskProcessor,HomeController, andLogisticsControllerpoint and audit flows onto their existingEHRContext, and removed those live paths from the hidden second-context pattern that was still writing point history and audit rows throughAppSettings.CreateContext(). - Removed the orphaned
SessionData(string username, IPrincipal user)constructor-sideAppSettings.CreateContext()load fromModels/Models.csand leftSessionDataas a plain data container, since the type no longer has any live call sites in the repo. - Centralized the active
APIHelperauth and logging context creation behind a localCreateContext()helper, extracted shared user and scope token-refresh persistence and bad-request update routines, and moved the active ESI access logging, token refresh, user-scope refresh, price lookup cache fill, ID search, director check, and name-resolution paths off scattered directAppSettings.CreateContext()calls. - Removed the remaining live
APIHelpersearch overload that reloadedUserby ID from the database, switched the logistics pricing and parse-items flows to pass the already-knownUserobject instead, and dropped that hidden lookup path from the active search call chain. - Marked the
OrderProcessor(ProcessServices)constructor as the DI activation path so service-provider validation no longer sees an ambiguous choice between the DI-backed process constructor and the manualOrderProcessor(EHRContext)controller path. - Made the
OrderProcessorservice registration explicit inProgram.csso DI now always constructs it fromProcessServicesinstead of reflecting over the available constructors during service validation. - Made the
UpdateRefiningProcessservice registration explicit inProgram.csso DI now always constructs it fromProcessServicesinstead of reflecting over the available constructors during service validation. - Added
IPayoutWorkflowService/PayoutWorkflowService, registered it in DI, and moved the activeHomeControllerhaul-submit, mining-op lifecycle, op-confirmation, and bounty-refresh paths off directnew PayoutProcess(_Context)construction while also removing the dead confirm-flowPricingProcessallocations in that block. - Added
IPricingWorkflowService/PricingWorkflowService, registered it in DI, removed the dead haul-sidePricingProcessconstruction inHomeController, and moved the activeHomeControllerore-price-per-m3 and price-adjustment cache fill plusSetupController.GetPrices()refresh flow off direct controller-ownednew PricingProcess(...)construction. - Extended
AppSettingswith DI scope creation, expandedIShortcutService/ShortcutServiceto cover the remaining static shortcut operations, and convertedHomeControllerUtilities.ShortCutsinto a thin compatibility wrapper that resolves DI services per call so the existing Razor, process, and legacy helper call sites no longer create their ownDbContext, lookup client, or character client instances directly. - Extended
ICoreWorkflowService/CoreWorkflowServicewith a fleet-points path and movedFleetController.Confirmation(...)off directnew CoreProcess(_Context)construction, leaving the fleet confirmation action on its existing request context while removing the last controller-owned core-process creation in that file. - Extended
ICoreWorkflowServiceandIPricingWorkflowServicewith context-aware killmail, bounty-point, and sale-price operations, then movedKillMailProcessand the activePayoutProcessbounty and ledger-payout paths off directnew CoreProcess(...)andnew PricingProcess(...)ownership by resolving those workflow services through the shared compatibility scope instead. - Extended
IPayoutWorkflowServicewith a context-aware bounty loader, removed the deadOrderProcessorPayoutProcessallocation, and moved the activeTaskProcessorbounty sweep and mining-op scheduler payout refresh paths off directnew PayoutProcess(_Context)construction and onto the shared payout workflow service. - Removed the remaining dead internal
PricingProcessself-construction inGetPrice(...)andUpdatePrice(...)plus the unusedPayoutProcessallocation inAddOpMinerals(...), trimming the leftover process-to-process noise fromPricingProcess.cswithout changing runtime behavior. - Moved the active
LedgerProcessormining-ledger price resolution off the staticShortCuts.GetPrice(...)bridge and ontoIPricingWorkflowServiceresolved through the shared compatibility scope, so those live process paths now use the request/processEHRContextdirectly instead of bouncing through the legacy shortcut wrapper. - Moved
ShortcutServiceprice lookups ontoIPricingWorkflowServiceso the service no longer constructsPricingProcessdirectly, and fixed theGetPrice(..., pricingTypeId)overload so it now actually passes the requested pricing type through instead of silently ignoring it. - Changed the DI-created
APIHelperpath to createEHRContextinstances from injectedIDbContextFactory<EHRContext>instead of the staticAppSettingsbridge, moving the live token-refresh, ESI-access logging, cached name lookup, price fill, and director-role helper paths onto the shared factory-backed context pattern. This also fixes the staleIsInDirector(string)cache branch so non-directors are no longer cached as directors. - Changed
SignalRServiceto use injectedIDbContextFactory<EHRContext>for its corp-user and op-member lookup helpers, removing the remaining real-time membership lookup paths from directAppSettings.CreateContext()usage. - Changed
Models.KillmailNamesto resolveILookupApiClientthrough the shared compatibility scope instead of constructing a rawAPIHelper, removing the last livenew APIHelper()path from the killmail Discord embed helper. - Changed
ParseHelperto use injectedIPricingWorkflowServicefor buyback preview pricing and changedNotificationsHelperto resolveIShortcutServicethrough the shared compatibility scope for notification name, item, and price formatting, removing another live batch of directShortCutscompatibility-wrapper calls from helper code. - Added
IShortcutServiceto the process-service bundle and moved the liveTaskProcessorstructure-fuel and Discord role-removal message formatting paths off the staticShortCutscompatibility wrapper and onto the injected shortcut service, while keeping default legacy processes on a small fallback adapter. - Fixed the
NotificationsHelperscoped-service lifetime regression by keepingIShortcutServiceinside the active compatibility scope for the full formatting pass, and added context-awareLog.Error(...)/Log.Audit(...)overloads soJsonErrorResultnow writes its error log with a singleEHRContextinstead of opening separate read and write contexts on the same error path. - Changed
HomeControllerUtilities.DropDownsto create its context through the registeredIDbContextFactory<EHRContext>resolved from the compatibility scope instead of callingAppSettings.CreateContext()directly, removing the last direct static context bridge from that utility file. - Collapsed
HomeControllerUtilities.ShortCutsonto a single scopedIShortcutServiceexecution helper and changed theBaseProcesslegacy shortcut fallback to resolveIShortcutServicedirectly from DI instead of bouncing back throughControllers.ShortCuts, reducing duplicate compatibility-wrapper code while keeping the existing Razor and default-process call sites working. - Removed another batch of direct
AppSettings.CreateContext()usage by moving theBaseProcessfallback path,KillMailProcess,ErrorLogging,JsonErrorResult,AuditLog,UserPoint, and the remainingAPIHelperfallback path ontoIDbContextFactory<EHRContext>resolved from the shared compatibility scope instead of the old static context bridge. - Tightened the active
HomeControllerguard paths by makingIndex()tolerate a missing identity, returningEmptyResultinstead ofnullfromCorpHeader(), and validating usernames or test users before calling the skill-refresh, bounty-load, and mining-ledger helper paths. - Tightened another
HomeControllernullability slice by making the internalGetUserInfo(...)helper nullable-aware, guarding loyalty-order notification sends behind a real username, returning a JSON success response instead ofnullfor no-op haul corrections, returningEmptyResultfrom the getting-started partial when no work is needed, and replacing the brokenDateTime == nullCSV export checks with explicit empty-date validation. - Removed another
HomeControllerwarning cluster by dropping the impossible calendar-events null return, validating the current username and user before building the skills and planets views, and makingGetErrors()tolerate missing identities while filtering null log entries before the final grouped JSON response. - Tightened the
HomeControlleralt-removal and monthly-income paths by guarding current-user name lookups before corp resolution, making the live monthly-income builder bail out cleanly when no current user is available, making the error-list accumulator explicitly nullable, and marking the monthly-income summary view model's optional highlight cards as nullable when a period has no matching categories. - Made the shared
BaseController/OAuthFlowHelperauthorization URL helpers accept nullable return and state values, tightenedGetSafeLocalReturnUrl()around missing referers, and added current-user guards to the activeUserControlsControllermail flows before loading users or checking ESI mail scopes. - Tightened the remaining
UserControlsControllercurrent-user and character-management paths by guarding the blacklist search, characters, scope purge, daily graphs, point-merge, wallet journal, main-character swap, and alt-removal actions, and by giving the chartDataSetDTO safe default string values instead of leaving those fields uninitialized. - Removed the remaining nullable-signature warnings from
JsonErrorResultandErrorLogging, hardened the reset-password partial against a missing identity, tightened the activeAccountControllerauth and callback paths with nullable-safe parameters and guards, and cleaned upUserExtensionswith safe snapshot defaults, nullable-aware helper signatures, andRandomNumberGenerator-based list shuffling. - Tightened another
HomeControllerslice by guarding scheduled-task deletion and creation, fitting asset search, SMS gateway updates, panic-message editor/send/delete flows, getting-started, alt removal, and nav info against missing records or identities, while also finishing the remaining easy nullable and auth fixes inAccountController.
Fixes
- Removed the stale commented-out code blocks that were still scattered through the active controllers, helpers, processes, and app-owned JavaScript files, leaving only real explanatory comments and TODO notes in the live source.
- Changed the Eve SSO callback token-persistence flow so Eve-HR now only stores the fresh token pair after the callback scope set satisfies the corporation's required scopes, and it now sends the user back through the correct corp-scope re-auth flow when neither the callback token nor the stored token matches the corp requirement.
- Fixed the authenticated recruitment apply flow so it now re-verifies the live token scopes before redirecting for SSO and only creates the application record after the scope check passes, preventing false scope redirects from stale stored scope strings.
- Fixed the Setup refining-skills action so it now uses the newer responsive setup button styling and a proper action row, preventing
Set My Skills For Mineral Refiningfrom clipping or collapsing inside the panel. - Fixed authenticated recruitment applications so the app now compares normalized requested and granted ESI scope sets instead of naive comma-split substring checks, preventing unnecessary re-auth prompts when your scopes have not changed.
- Fixed the background mining-op ledger refresh so scheduled updates now load active op members from the database instead of relying on an unloaded
OpMembersnavigation, preventing live ops from appearing to have no active members during the scheduler pass. - Fixed the Eve SSO callback so fresh logins now immediately adopt the newly returned token pair and clear stale revoked-token state before the follow-on scope and ESI checks run, preventing valid sign-ins from failing with a restore-ESI error.
- Fixed the buyback parse preview database path by ignoring the dead EF
Haul.Itemsnavigation that was generating the invalid MySQLi.HaulIdquery, and tightenedParseHelperso newly resolved items are only inserted once before preview history is built. - Fixed the shared buyback preview partial so it now renders the correct submit-button class for the home dashboard versus the public buyback page, restoring the final
Submit Orderclick on the authenticated buyback screen. - Fixed the authenticated home buyback submit handler so successful submissions now show a real completion state and failed submits surface the server error instead of appearing to do nothing.
- Changed the shared home and public buyback submit buttons so once clicked they immediately switch to a muted
Submittedstate and disable further clicks while the request is in flight, reducing duplicate submissions and making the control read as completed instead of active. - Replaced the scope re-auth page's plain text link treatment with dedicated standalone CTA button styling so
Continue to Re-authandBack to Loginread like real buttons on the authorization screen. - Replaced the legacy tiny
Submitinput on the buyback setup tax-rates card with a dedicated styledSave Settingsaction button so that panel now matches the newer setup UI. - Added responsive breakpoints to the Pricing Control page so the calculator list, tab bar, override form, and formula builder step cards now stack and shrink cleanly on narrower widths instead of holding the oversized desktop layout.
- Changed mining-op ledger refresh so per-character ledger fetch failures, null payloads, and empty-result passes are treated as non-fatal skips, allowing the op update to continue across every linked character instead of aborting on the first bad ledger response.
- Fixed grouped mining-op ledger expiration handling so a main-alt op member now keeps the freshest successful ledger authorization across linked characters instead of being falsely marked stale by another linked character's older or failed token.
- Removed the obsolete
Price AdjustmentsandPrice Overridesentries from the buyback sidebar so only the remaining supported setup tools stay in navigation. - Removed the dead controller-owned recruitment snapshot and cache helper block from
RecruitmentControllerafter moving the live path ontoIRecruitmentSnapshotService, so the hosted snapshot service is now the only active implementation. - Removed the mutable process-layer error flags from
BaseProcess, replaced the oldError()/HasError()/ErrorMessage()flow with exception-backedProcessResultcapture, and converted the active core, ledger, payout, pricing, parse-items, and task-processor callers onto shared typed client properties instead of per-call factory helpers. - Reduced the remaining direct
APIHelperusage inHomeControllerby moving buyback parsing ontoILookupApiClient, swapping more name and universe lookups onto narrower clients, and converting the staticShortCutshelper to use lookup and character clients instead of raw helper calls. - Removed the raw
APIHelperdependency from the recruitment mailer helper and kept the mailer flow on typed lookup and mail clients. - Removed the mutable
APIHelpercontroller error-state compatibility layer and moved the typed operation wrappers onto direct exception-to-result handling so helper calls no longer share request-local error flags. - Replaced the remaining
HomeControllerWebClientcalls withHttpClient, switched the flagged partial views to async-safe<partial>rendering, and removed the startup raw-SQL interpolation warning by using fixed SQL strings. - Finished the
BaseControllerconstructor-injection transition through the sharedControllerServicesbundle, moved anotherHomeController,RecruitmentController, andTaskProcessorslice off rawAPIHelpercalls, and removed the blocking wait fromKillMailProcessshutdown by switching it to cancellation plusStopAsync(). - Finished the
APIHelperESI refactor by migrating the remaining contacts, contracts, mail, wallet, structures, and related endpoints offWebClient, and removed the old token-refreshTask.Run/.Wait()sync-over-async flow. - Reduced the next-phase API refactor risk by moving injected
APIHelperusage off singleton lifetime, adding a typed internal HTTP result path for clearer failures, and making the calendar background worker honor cancellation instead of blocking on rate-limit delays. - Started replacing the legacy
HasError()controller contract by adding typed operation-result wrappers for mail, wallet, notifications, and market orders, and migrated the main User Controls and Recruitment screens for those flows onto the new pattern. - Extended the typed-result migration to structures, fleet, mining-ledger, skills, and skill-queue reads, and fixed the active-fleets partial so assembled fleet view models are actually returned for rendering.
- Continued the typed-result migration through alliance banner/member checks, SRP killmail loading, setup skill lookups, mining-op contract validation, fitting asset scans, and task/ledger background processing so more ESI paths no longer depend on mutable
APIHelpererror state. - Moved the auth and Discord role-assignment paths onto the newer typed wrappers for alliance, titles, corp affiliations, alt token refresh checks, and explicit-token character role reads.
- Pushed the typed-result migration deeper through recruitment snapshots, contacts/history/contracts/killmail views, wallet name-resolution paths, SRP killmail aggregation, and the director-role helper so those screens no longer depend on ad hoc
HasError()checks. - Added a real async token-refresh persistence path for async callers, moved the account callback and task processor onto it, and converted another
HomeControllerslice for names, character/corporation lookups, status, planets, and skills onto typed helper wrappers. - Added explicit result wrappers to
LedgerProcessorfor start/join/update flows, moved the mining-op controller and background call sites onto them, and converted the nav summary plus remaining contract-role checks inHomeControlleronto typed helper wrappers. - Extended the explicit-result pattern into
PayoutProcessandCoreProcess, moved the active mining-op and notification call sites onto those results, and replaced the old fire-and-forgetUpdateSkillserror check with a direct result-based refresh. - Converted
ParseHelperto an explicit parse-result contract for the active buyback order and haul preview flows, removing the last live parserHasError()checks from thatHomeControllerslice. - Added typed wrappers for corporation journal transactions and fittings, and moved the remaining monthly-income, mining-payment, fitting-sync, and contract-item validation paths in
HomeControllerandMiningControlleroff raw helper calls. - Added typed wrappers for blacklist search and blacklist name resolution, and moved the remaining alliance-management and user-controls search, member lookup, journal, and name-resolution paths off raw helper calls.
- Added typed wrappers for universe, item, route, and alliance-history lookups, and moved the logistics controller, lookup popups, account item import, recruitment mailer/search paths, and the remaining home/task/discord/core-process raw helper callers onto explicit operation results.
- Added typed wrappers for ID resolution, corp-title reads, and bulk-price fetches, then moved the remaining SRP history, recruitment pricing/mailer, Discord title sync, task-processor fleet and structure polling, and core-process skill-refresh reads off raw helper calls.
- Added typed wrappers for market-price, buy/sell, and direct mail-send helper calls, then moved the remaining pricing fallback paths, subscription mail sender, and parse-time item resolution off raw helper calls while fixing the null pricing-mapping branch in
PricingProcess.GetPrice. - Extracted reusable OAuth URL and state helpers, moved controller scope-authorization redirects off ad hoc
APIHelperconstruction, and wired the fleet, structure, mining, contracts, and custom-scope flows onto explicit state-token generation with their callback flow names preserved. - Centralized named-scope checks behind a shared helper, fixed the brittle fleet-scope substring check to evaluate required scopes individually, removed dead
APIHelperscope wrappers, and trimmed more obsolete auth compatibility code fromAPIHelper. - Centralized controller-side
APIHelperaccess behind a sharedBaseControllerhelper and replaced the remaining active controllernew APIHelper(this)call sites so ESI-backed controller flows no longer construct ad hoc helpers throughout each file. - Centralized process-side
APIHelperconstruction behind a sharedBaseProcessfactory helper and replaced the activenew APIHelper()calls across task, pricing, payout, core, ledger, and parsing processes so the remaining background ESI flows stop scattering direct helper construction through each method. - Replaced the last active ad hoc
APIHelperconstructions inShortCuts, recruitment snapshot refresh workers, and the internal director-role cache path with small shared helper and factory methods so only the intentional centralized creation points remain. - Added a dedicated
ILookupApiClient/LookupApiClientservice for read-only universe and reference-data calls, registered it in DI, exposed it throughBaseController, and movedLookupControllerplus the logistics route, item, system, and search flows onto that narrower client instead of the fullAPIHelperfacade. - Added a dedicated
IMailWalletApiClient/MailWalletApiClientservice for mail, wallet, notifications, and market-order ESI calls, registered it in DI, exposed it throughBaseController, and moved the mainUserControls,Recruitment, andHomeControllermail/wallet flows onto that narrower client. - Added a matching process-side mail/wallet client factory in
BaseProcessand moved the activeTaskProcessornotification and advert-wallet polling paths plusCoreProcesssubscription mail sending ontoIMailWalletApiClientinstead of the broaderAPIHelperfacade. - Added a matching process-side lookup client factory and moved the remaining active character, corporation, and alliance reads in
AllianceController,AccountController,DiscordController,HomeController,RecruitmentController, andCoreProcessontoILookupApiClientinstead of directAPIHelperlookup calls. - Added a dedicated
IAuthApiClient/AuthApiClientservice for SSO verify and token-refresh operations, exposed it through the shared controller and process helpers, moved the active account callback and token-refresh worker paths onto it, and removed the deadISingleSignOnClientregistration and unusedAPIHelperinterface shim. - Removed the misleading synchronous
GetRefreshTokenAsyncpath insideAPIHelper, renamed the remaining sync and async token refresh helpers to clearer internal names, and rewired the shared access-check path onto a newEnsureAccessTokenhelper without changing caller behavior. - Added a dedicated
ICharacterApiClient/CharacterApiClientservice plus controller and process factory accessors, and moved the active roles, skills, skill queue, attributes, contacts, corp-history, assets, structure-name, location, ship, and online-state reads across Home, Recruitment, Fleet, Fitting, Account, Alliance, Setup, Structure, TaskProcessor, and CoreProcess off directAPIHelpercharacter-profile calls. - Added a dedicated
IOperationsApiClient/OperationsApiClientservice plus controller and process accessors, and moved the active fleets, contracts, mining-ledger, calendar, and planet reads across Fleet, Home, Mining, Recruitment, TaskProcessor, and LedgerProcessor off directAPIHelperoperations calls. - Added a dedicated
ICorporationApiClient/CorporationApiClientservice plus controller and process accessors, and moved the active corporation members, corp-contract, corp-title, structure-info, character-title, and corp wallet-journal reads across Alliance, Home, Discord, Structure, UserControls, Mining, TaskProcessor, and PayoutProcess off directAPIHelpercorporation calls. - Added a singleton
IRecruitmentSnapshotServicewith hosted background refresh processing for skills and assets, moved the active recruitment mini views and snapshot-backed endpoints onto async service calls, and centralized the live cache and refresh path outsideRecruitmentController.
Changes
- Removed the ad-cookie approval banner and manual advertising-consent flow, and simplified the privacy/footer links so the site no longer exposes cookie-settings controls.
Fixes
- Fixed mining-op ledger aggregation so refreshes, joins, leave-op handling, and payout snapshots resolve through the root main account and linked sibling alts.
- Fixed mining warning copy and stale-ledger detection so empty mining results no longer trigger false member warnings.
- Fixed ESI token refresh persistence and moved mining-ledger/corporation-member ESI calls onto a safer HTTP path with stricter response handling.
Changes
- Added the shared sidebar collapse toggle and then refined it into a lighter chevron-only control.
Fixes
- Fixed the Mining Ops timer, end-operation refresh flow, live page auto-refresh, and multiple Mining Ops UI alignment issues across the redesigned command deck.
- Expanded mining ledger warning details, tightened Lucky Slot Machine winner privacy, and refreshed the shared confirmation dialog styling.
- Fixed the Setup/Prices step-number rail alignment regression.
Changes
- Updated the patch notes workflow so new changes create the correct current-day section and the in-app Patch Notes page stays synchronized with
PATCHNOTES.md. - Added a real on-page
3/8/2026patch notes card and refreshed the rendered summaries so today’s work is visible in the app. - Redesigned the standalone scope re-authentication screen to match the newer EVE-HR visual language.
- Expanded Lucky Slot Machine stats with total spins, total wins, total win rate, last winner, and progressive winnerless-day odds.
- Updated Bug Admin so closed reports move into an expandable history section instead of remaining in the active queue.
Fixes
- Fixed Corp Members ESI handling around claimed-director scope refresh, alliance-exec queries, and error reporting for failed external member checks.
- Fixed bug report admin navigation, filter alignment, and detail-page chevron positioning.
- Fixed Mining Ledger, Mining Ops CSV, and Fittings UI alignment problems caused by legacy button and checkbox styling.
- Fixed the Bulletins Calendar Events layout by replacing the problematic table treatment with a cleaner board layout.
Changes
- Added the full in-app bug reporting system with required screenshots, personal tracking, private image storage, and an Ascorbic-only admin queue.
- Expanded release tooling with a dedicated publish-zip script, VS Code task support, and a tracked
releases\output folder. - Refreshed major UI areas including bug reports, Director Only, Alliance Join, Fleet tools, SRP, ESI Audit, Recruitment setup pages, and the Price Builder to better match the newer EVE-HR theme.
- Added a shared SVG favicon so browser tabs and bookmarks use EVE-HR branding.
Fixes
- Fixed bug report submission, routing, explicit navigation paths, and admin screenshot previews.
- Fixed Add Alt and recruitment application OAuth issues including stale scope validation, oversized callback state, and post-auth redirect/login expiry problems.
- Improved performance on User Management, Recruitment Current Member, and page authorization checks by reducing duplicate queries and parallelizing initial loads.
- Fixed multiple legacy UI regressions including broken toggles, clipped buttons, white/grey editors, and inconsistent page layouts across Recruitment, Alliance, Fleet, and Setup screens.
Fixes
- Recruitment Current Member: fixed the Corp History sidebar list so logos, names, and dates render in a readable compact layout.
- Recruitment Current Member: corp names in Corp History now truncate cleanly with ellipsis instead of overflowing the sidebar.
- Recruitment Current Member: removed the unnecessary secondary Corp History label text to reduce visual clutter.
Changes
- Recruitment ads: updated Patreon Supporters to use the same card presentation as Featured Corporations, with a Patron-specific accent treatment.
- Recruitment Corp History: refreshed the full and mini card styling to better match the current site theme.
Fixes
- Navigation accordion spacing and background-color regressions were corrected across the sidebar and loyalty pages.
- Personal Mining History fixes addressed row overlap, readability, and Export CSV button rendering.
- Logistics Orders row expansion and clipboard-copy behavior were corrected for the redesigned layouts.
- Loyalty/Gifting now validates and loads the same accessible member scope before points are applied.
Changes
- Rebuilt the left navigation as an accordion and standardized the newer blue-theme navigation styling.
- Redesigned Loyalty Pending Orders, Personal Mining History, User Management, User Store, Logistics Orders, Point History, and Gifting.
- Applied a broader legacy-page theme override and added runtime toggles for background hosted services.
Changes
- Improved pricing performance by fetching missing mineral prices in parallel instead of serially during cache misses.
Changes
- Large recruitment performance pass: added cached mini/snapshot endpoints, stale-while-revalidate behavior, shared skill/name caches, lighter JSON mini payloads, and deferred/lazy rendering for expensive Current Member sections.
- Optimized Corp Members with cached corp-name resolution and reduced per-corp lookup overhead.
Fixes
- Fixed several recruitment null/stale-token error paths and corrected
Setup/DirectorOnlyfallback handling when the Director claim is stale or missing.
Fixes
- Removed loading elapsed time/counter text from Ore Values loading states.
- Removed link-level block overlay on Buy Back navigation links so the dark box no longer appears over link text.
- Patron access checks now read from auth snapshot/corp state instead of stale claim-only paths, so enabled patron status applies correctly.
- Killmail broadcast dedupe now uses a consistent corp/alliance key path, preventing duplicate or skipped attacker broadcasts.
Changes
- Ore Values: replaced the small legacy spinner with a new centered animated orbit loader and consistent loading message.
- Ore Values: updated AJAX body loading to immediately clear old content and show loading UI while prices are calculated.
- Price Adjustments: migrated to the same AJAX body-load flow used by Ore Values, including centered loader and in-panel render.
- Navigation: added shared Home panel loading helper for consistent loading/error UI across GetPricePerM3 and PriceAdjustments.
- Setup: added a Patreon validation action/button that checks active patron status and enables corp patron benefits (
Corp.Patron) when valid. - Setup UX: moved Patreon Benefits into a dedicated
Setup/PatreonBenefitspage and added a separate Setup navigation link. - Ads: refactored layout ad integration to one global AdSense script include with responsive ad slots and reserved ad space to reduce layout shift.
- Ads: removed custom ad-block pop-up flow so AdSense-managed ad-block recovery/offerwall can be used instead.
- Ads platform: added
ads.txtat/ads.txtfor AdSense crawler discovery. - Killmail worker: added periodic cache refresh during runtime, payload null guards, and host-linked cancellation/shutdown handling.
Fixes
- Recruitment/Assets: fixed
location_flagparsing so new ESI values likeInfrastructureHangarno longer throw a 500 during asset load. - Recruitment/Assets: updated structure filtering to treat both
HangarandInfrastructureHangaras valid structure hangar locations. - Recruitment/CurrentMember: fixed the GetAssetInfo sort toggle click handler so sorting now reliably toggles between total ISK and alphabetical order.
Changes
- Recruitment/Assets: added a sort toggle in GetAssetInfo so asset locations can switch between alphabetical and highest total ISK ordering.
- Documentation: added USER_GUIDE.md with user-facing workflows for navigation, recruitment, mining, logistics, loyalty points, and setup roles.
- Documentation: expanded USER_GUIDE.md with a detailed, step-by-step setup/configuration tutorial for admins, including director scopes, pricing setup, permissions, scope policy, Discord, structure alerts, and recruitment setup.
Fixes
- Recruitment: fixed GetSkillInfo so returned JSON includes populated SkillQueue and SkillsList content.
- Recruitment: fixed GetAssetInfo rendering path so assets returned from API now display correctly on-screen.
- Recruitment UI: updated AJAX handlers to support object JSON first, with safe string-JSON fallback and clearer error display.
- Controllers: fixed JSON error paths that called JsonHttpStatusResult.Error(...) without returning the result.
- Setup/UserControls: removed double-serialized JSON responses and aligned payload contracts with current JS consumers.
- Logistics: improved pricing validation so invalid inputs return explicit JSON errors instead of silent failures.
Changes
- Added Patch Notes link to the main left navigation menu.
- Refreshed Recruitment CurrentMember UI with cleaner tabs, improved character summary layout, and responsive styling.
- Adjusted CurrentMember colors to match the existing site theme (dark gray/black styling).
- Updated Skills/Fittings tabs to rectangular tab styling and increased font sizes for easier reading.
- Moved patch notes to Patreon. You can view them here.
Fixes
- Structure alerts should no longer alert every hour, but only once a day.
- Hourly user updates should now actually update their corp information.
New Features
- Loyalty Point Monthly Reset.
Fixes
- Fixed issue with Preview not showing results if item was not found.
New Features
- Loyalty Point History.
Fixes
- Removed New Member Page from setup, was already under recruitment.
- Fixed some typos.
New Features
- !who command now allows you to check who alts belong to in your corp.
Fixes
- Fixed issue with leader of mining op leaving breaking the op.
- Fixed issue with mining ledger failing to update when the main user has an error.
- Increased max value of loyalty points to 9,007,199,254,740,992.
New Features
- Discord Notification for New Mining Ops.
Fixes
- Fixed issue with check box on system settings.
New Features
- Members Page got a facelift.
- Members Page got a Search Button.
Fixes
- Type fixed on logistics page.
New Features
- Added Personal Mining Op History.
Fixes
- Taxes can now have decimals.
New Features
- Added Preview Sale Order function.
- Added Cancel button to Loyalty Points order confirmation page.
Fixes
- Added enum to Journal for gate jumping.
New Features
- Added Mining Ops Histories.
Fixes
- Fixed type on Setup/Setup for efficiency.
- Fixed for parsing ascii characters during submitting sale orders.
- Updated confirmation text for removing discord link from eve-hr.
- Fixed Payout Calculations, you should now see outstanding payments due.
- Fixed loyalty points for mining/sell orders.
New Features
- Mining Invoicing!
- New Bounty System!
Fixes
- Issue with Access Tokens becoming out of sync has been resolved.
- Renamed Rems to Rens...
New Features
- Mining Ledger Viewer now has Include Alts option.
Fixes
- Inverted navigation bar controls. It will now stay expanded until you click on EveHR to minimize it.