Release Healer Man 0.1.32

This commit is contained in:
phenom committed 2026-09-12 11:07:25 -04:00
1 parent d3d6aaebdb
commit 6dcb1034e5
102 files changed
+10772 -788

No files matched your search

+5 -18
View File
@@ -1,4 +1,4 @@
import { lazy, Suspense, useState, type CSSProperties } from "react";
import { useState, type CSSProperties } from "react";
import { classById, FACTIONS, raceById } from "../app/characterCatalog";
import type { CharacterProfile } from "../app/types";
import { useShellStore } from "../app/shellStore";
@@ -28,14 +28,8 @@ import { ActionBar, type ActionBarItem, type ActionBarItems } from "./ActionBar"
import { DungeonMapGraphic } from "./DungeonMapGraphic";
import { ExperienceBar } from "./ExperienceBar";
import { BrandMark, FrontSurface } from "./FrontSurface";
import {
getLoadedCompanionManastormPanel,
loadCompanionManastormPanel,
} from "./gameplayResources";
const CompanionManastormPanel = lazy(() => loadCompanionManastormPanel().then((module) => ({
default: module.CompanionManastormPanel,
})));
import { ShelvedCompanionPanel } from "@shelvedCompanionPanel";
import { SHELVED_MODES_ENABLED } from "../app/releaseFeatures";
function safePercent(current: number, maximum: number): number {
if (!Number.isFinite(current) || !Number.isFinite(maximum) || maximum <= 0) return 0;
@@ -158,7 +152,6 @@ export function CompanionPanel({ snapshot, send, preferControllerBinds = false }
}) {
const selected = snapshot.characters.find((character) => character.id === snapshot.selectedCharacterId) ?? null;
const active = snapshot.activeCharacter ?? selected;
const PreloadedCompanionManastormPanel = getLoadedCompanionManastormPanel();
if (snapshot.phase === "login") {
return (
@@ -289,7 +282,7 @@ export function CompanionPanel({ snapshot, send, preferControllerBinds = false }
<footer className="companion-actions">
<button type="button" onClick={() => send({ name: "returnToCharacters" })}>Change character</button>
<button type="button" disabled={!selected} onClick={() => send({ name: "openDungeons" })}>Dungeons</button>
<button type="button" disabled={!selected} onClick={() => send({ name: "openManastorms" })}>Manastorms</button>
{SHELVED_MODES_ENABLED ? <button type="button" disabled={!selected} onClick={() => send({ name: "openManastorms" })}>Manastorms</button> : null}
</footer>
</FrontSurface>
);
@@ -300,13 +293,7 @@ export function CompanionPanel({ snapshot, send, preferControllerBinds = false }
}
if (snapshot.phase === "manastorms") {
return PreloadedCompanionManastormPanel ? (
<PreloadedCompanionManastormPanel selected={selected} send={send} />
) : (
<Suspense fallback={<FrontSurface bottom className="companion-panel" ariaLabel="Loading Manastorm companion screen"><span>Loading Manastorm data…</span></FrontSurface>}>
<CompanionManastormPanel selected={selected} send={send} />
</Suspense>
);
return <ShelvedCompanionPanel selected={selected} send={send} />;
}
const race = active ? raceById(active.raceId) : null;
+26 -8
View File
@@ -6,6 +6,14 @@ const content = vi.hoisted(() => ({
installed: true,
update: true,
bundled: false,
packs: [{
id: "characters:test",
label: "Test Characters",
bytes: 1024,
fileCount: 1,
bundledOnAndroid: false,
dependencies: [],
}],
install: vi.fn(async () => undefined),
remove: vi.fn(async () => undefined),
}));
@@ -29,14 +37,7 @@ vi.mock("../content/useContentManager", () => ({
checking: false,
manifest: {
version: "content-test",
packs: [{
id: "characters:test",
label: "Test Characters",
bytes: 1024,
fileCount: 1,
bundledOnAndroid: false,
dependencies: [],
}],
packs: content.packs,
},
bundledManifest: null,
installingPackId: null,
@@ -61,6 +62,10 @@ describe("browser Downloads actions", () => {
content.installed = true;
content.update = true;
content.bundled = false;
content.packs = [{
id: "characters:test", label: "Test Characters", bytes: 1024,
fileCount: 1, bundledOnAndroid: false, dependencies: [],
}];
});
it("offers an update action when an installed browser pack has changed", () => {
@@ -92,4 +97,17 @@ describe("browser Downloads actions", () => {
expect(markup).toContain("Included — always available");
expect(markup).not.toContain(">Remove</button>");
});
it("hides shelved and internal dependency packs from Downloads", () => {
content.packs = [
...content.packs,
{ id: "core:arenas", label: "Rated Arenas", bytes: 1, fileCount: 1, bundledOnAndroid: false, dependencies: [] },
{ id: "manastorm:deadmines", label: "Deadmines Manastorm", bytes: 1, fileCount: 1, bundledOnAndroid: false, dependencies: [] },
];
const markup = renderToStaticMarkup(createElement(ContentManagerScreen, { onClose: () => undefined }));
expect(markup).toContain("Test Characters");
expect(markup).not.toContain("Rated Arenas");
expect(markup).not.toContain("Deadmines Manastorm");
});
});
+5 -1
View File
@@ -12,6 +12,7 @@ import { useContentManagerSnapshot } from "../content/useContentManager";
import { DualDisplayFrame } from "../components/DualDisplayFrame";
import { LazyLocalCompanionPanel } from "./LazyLocalCompanionPanel";
import { BrandMark, FrontSurface } from "./FrontSurface";
import contentPolicy from "../../shared/content-policy.json";
function formatBytes(bytes: number): string {
if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -23,7 +24,10 @@ function formatBytes(bytes: number): string {
export function ContentManagerScreen({ onClose }: { readonly onClose: () => void }) {
const state = useContentManagerSnapshot();
const native = Capacitor.isNativePlatform();
const packs = useMemo(() => state.manifest?.packs ?? state.bundledManifest?.packs ?? [], [state.bundledManifest, state.manifest]);
const packs = useMemo(() => (state.manifest?.packs ?? state.bundledManifest?.packs ?? []).filter((pack) => (
!contentPolicy.hiddenContentPackIds.includes(pack.id)
&& !contentPolicy.hiddenContentPackPrefixes.some((prefix) => pack.id.startsWith(prefix))
)), [state.bundledManifest, state.manifest]);
const progress = state.totalBytes > 0 ? Math.min(1, state.downloadedBytes / state.totalBytes) : 0;
return (
+2 -2
View File
@@ -30,7 +30,7 @@ import { EncounterTimeline } from "./EncounterTimeline";
import { PartyFrames } from "./PartyFrames";
import { TimedEffectStrip } from "./TimedEffectStrip";
import { AuraStrip } from "./AuraStrip";
import { ManastormStatus } from "./ManastormStatus";
import { ShelvedHud } from "@shelvedHud";
import { ThreatMeterWidget } from "./ThreatMeterWidget";
import { BossLootWindow } from "./BossLootWindow";
import { EditableHudElement } from "./EditableHudElement";
@@ -363,7 +363,7 @@ export function Hud() {
return (
<div className="hud" style={{ "--game-ui-scale": settings.uiScale } as CSSProperties}>
<ManastormStatus />
<ShelvedHud />
<BossLootWindow />
<EditableHudElement id="completion">{dungeonComplete && health > 0 ? (
<DungeonCompletePrompt dungeonTitle={dungeon.title} onFinish={finishDungeon} />
+11 -13
View File
@@ -8,8 +8,8 @@ import { LazyLocalCompanionPanel } from "./LazyLocalCompanionPanel";
import { ControllerButton } from "./ControllerButton";
import { ContentManagerScreen } from "./ContentManagerScreen";
import { BrandMark, ControllerLegend, FrontSurface } from "./FrontSurface";
import { isGameMasterSession } from "../game/manastormAdminConfig";
import { OnlineGroupPanel } from "./OnlineGroupPanel";
import { sessionCanUseShelvedGmTools, SHELVED_MODES_ENABLED } from "../app/releaseFeatures";
const GAME_MODES = [
{
@@ -19,20 +19,19 @@ const GAME_MODES = [
description: "Choose an expedition and lead your party into the depths.",
available: true,
},
{
id: "manastorms",
...(SHELVED_MODES_ENABLED ? [{
id: "manastorms" as const,
number: "02",
name: "Manastorms",
description: "Chain random boss encounters as the storm grows stronger and more rewarding.",
available: true,
},
{
id: "arenas",
}, {
id: "arenas" as const,
number: "03",
name: "Arenas",
description: "Rated 2v2 and 3v3 combat against players or rating-matched opponents.",
available: true,
},
}] : []),
{
id: "trials",
number: "04",
@@ -77,7 +76,7 @@ export function MainMenuScreen() {
const openGmAdmin = useShellStore((state) => state.openGmAdmin);
const character = activeCharacter ?? rosterCharacter;
const isRom = character?.categoryId === "rom";
const isGm = isGameMasterSession(session);
const isGm = sessionCanUseShelvedGmTools(session);
const actions = useMemo<MenuAction[]>(() => [
{
@@ -85,20 +84,19 @@ export function MainMenuScreen() {
run: openDungeons,
neighbors: { right: "mode-manastorms", down: "back-to-roster" },
},
{
...(SHELVED_MODES_ENABLED ? [{
id: "mode-manastorms",
run: openManastorms,
neighbors: { left: "mode-dungeons", down: "back-to-roster" },
},
{
}, {
id: "mode-arenas",
run: openArenas,
neighbors: { left: "mode-manastorms", down: "back-to-roster" },
},
}] : []),
{
id: "mode-downloads",
run: () => setContentManagerOpen(true),
neighbors: { left: "mode-manastorms", down: "back-to-roster" },
neighbors: { left: SHELVED_MODES_ENABLED ? "mode-manastorms" : "mode-dungeons", down: "back-to-roster" },
},
{
id: "back-to-roster",
+2 -2
View File
@@ -3,7 +3,7 @@ import { useShellStore } from "../app/shellStore";
import { useGameStore } from "../game/store";
import { useMenuController, type MenuAction } from "../input/useMenuController";
import { ControllerButton } from "./ControllerButton";
import { isGameMasterSession } from "../game/manastormAdminConfig";
import { sessionCanUseShelvedGmTools } from "../app/releaseFeatures";
import { useInputBindingLabels } from "../input/useInputBindingLabels";
import { INPUT_ACTION_BY_ID } from "../input/keybinds";
@@ -33,7 +33,7 @@ function PauseDialog() {
abandonManastormRun();
};
const skillMenuLabel = activeCharacter?.categoryId === "rom" ? "Skills" : "Talents";
const isGm = isGameMasterSession(session);
const isGm = sessionCanUseShelvedGmTools(session);
const actions = useMemo<MenuAction[]>(() => [
{ id: "pause-resume", run: resume },
@@ -0,0 +1,9 @@
import type { CharacterProfile } from "../app/types";
import type { CompanionCommand } from "../platform/dualScreenSync";
export function ShelvedCompanionPanel(_props: {
readonly selected: CharacterProfile | null;
readonly send: (command: CompanionCommand) => void;
}) {
return null;
}
+22
View File
@@ -0,0 +1,22 @@
import { lazy, Suspense } from "react";
import type { CharacterProfile } from "../app/types";
import type { CompanionCommand } from "../platform/dualScreenSync";
import { FrontSurface } from "./FrontSurface";
const CompanionManastormPanel = lazy(() => import("./CompanionManastormPanel").then((module) => ({
default: module.CompanionManastormPanel,
})));
export function ShelvedCompanionPanel({
selected,
send,
}: {
readonly selected: CharacterProfile | null;
readonly send: (command: CompanionCommand) => void;
}) {
return (
<Suspense fallback={<FrontSurface bottom className="companion-panel" ariaLabel="Loading Manastorm companion screen"><span>Loading Manastorm data…</span></FrontSurface>}>
<CompanionManastormPanel selected={selected} send={send} />
</Suspense>
);
}
+3
View File
@@ -0,0 +1,3 @@
export function ShelvedHud() {
return null;
}
+5
View File
@@ -0,0 +1,5 @@
import { ManastormStatus } from "./ManastormStatus";
export function ShelvedHud() {
return <ManastormStatus />;
}
+7 -2
View File
@@ -21,6 +21,10 @@ const companionPanelSource = readFileSync(
new URL("./CompanionPanel.tsx", import.meta.url),
"utf8",
);
const shelvedCompanionSource = readFileSync(
new URL("./ShelvedCompanionPanel.enabled.tsx", import.meta.url),
"utf8",
);
describe("gameplay panel loading integration", () => {
it("loads talent resources behind the main-menu catalog gate, not during gameplay", () => {
@@ -42,9 +46,10 @@ describe("gameplay panel loading integration", () => {
expect(gameplayResourcesSource).toContain('import("../GameRuntime")');
expect(gameplayResourcesSource).toContain('import("./PartyManagementPanel")');
expect(gameplayResourcesSource).toContain('import("./BossGuidePanel")');
expect(gameplayResourcesSource).toContain('import("./CompanionManastormPanel")');
expect(gameplayResourcesSource).not.toContain('import("./CompanionManastormPanel")');
expect(shelvedCompanionSource).toContain('import("./CompanionManastormPanel")');
expect(gameplayPanelsSource).toContain("<PreloadedPartyManagementPanel />");
expect(gameplayPanelsSource).toContain("<PreloadedBossGuidePanel />");
expect(companionPanelSource).toContain("<PreloadedCompanionManastormPanel");
expect(companionPanelSource).toContain("<ShelvedCompanionPanel");
});
});
-22
View File
@@ -4,8 +4,6 @@ let partyManagementPanelModule: Promise<typeof import("./PartyManagementPanel")>
let loadedPartyManagementPanel: typeof import("./PartyManagementPanel")["PartyManagementPanel"] | null = null;
let bossGuidePanelModule: Promise<typeof import("./BossGuidePanel")> | null = null;
let loadedBossGuidePanel: typeof import("./BossGuidePanel")["BossGuidePanel"] | null = null;
let companionManastormPanelModule: Promise<typeof import("./CompanionManastormPanel")> | null = null;
let loadedCompanionManastormPanel: typeof import("./CompanionManastormPanel")["CompanionManastormPanel"] | null = null;
export function loadGameRuntime() {
if (!gameRuntimeModule) {
@@ -64,31 +62,11 @@ export function getLoadedBossGuidePanel() {
return loadedBossGuidePanel;
}
export function loadCompanionManastormPanel() {
if (!companionManastormPanelModule) {
companionManastormPanelModule = import("./CompanionManastormPanel")
.then((module) => {
loadedCompanionManastormPanel = module.CompanionManastormPanel;
return module;
})
.catch((error: unknown) => {
companionManastormPanelModule = null;
throw error;
});
}
return companionManastormPanelModule;
}
export function getLoadedCompanionManastormPanel() {
return loadedCompanionManastormPanel;
}
export async function preloadSharedGameplayResources(originalMediaEnabled: boolean): Promise<void> {
await Promise.all([
loadGameRuntime(),
loadPartyManagementPanel(),
loadBossGuidePanel(),
loadCompanionManastormPanel(),
]);
if (!originalMediaEnabled) return;
const { ensureCombatMediaPack } = await import("../game/combatMediaPack");