From 6734d023435b0ae31b54322c7e3a7fdcd7cb869f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:03:36 +0000 Subject: [PATCH 1/9] Initial plan From cbaa393134fb044bff2f9db02d4b0ab2846a1d63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:13:06 +0000 Subject: [PATCH 2/9] Fix prefilled form fields, add tick countdown, improve workforce UI explanations Co-authored-by: BENZOOgataga <50145143+BENZOOgataga@users.noreply.github.com> --- .../src/components/layout/tick-countdown.tsx | 51 ++++++++++ apps/web/src/components/layout/top-bar.tsx | 6 +- .../market/order-placement-card.tsx | 8 +- .../components/production/production-page.tsx | 4 +- .../components/workforce/workforce-page.tsx | 96 ++++++++++++------- apps/web/src/lib/api-client.ts | 2 +- 6 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 apps/web/src/components/layout/tick-countdown.tsx diff --git a/apps/web/src/components/layout/tick-countdown.tsx b/apps/web/src/components/layout/tick-countdown.tsx new file mode 100644 index 00000000..d2fd573d --- /dev/null +++ b/apps/web/src/components/layout/tick-countdown.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Clock } from "lucide-react"; +import { InlineHelp } from "@/components/ui/inline-help"; +import { useWorldHealth } from "./world-health-provider"; + +// Default tick interval (60 seconds) - matches worker default configuration +// TODO: Get this from API configuration endpoint +const DEFAULT_TICK_INTERVAL_MS = 60_000; + +export function TickCountdown() { + const { health } = useWorldHealth(); + const [secondsRemaining, setSecondsRemaining] = useState(null); + + useEffect(() => { + if (!health?.lastAdvancedAt) { + setSecondsRemaining(null); + return; + } + + const updateCountdown = () => { + const lastAdvancedTime = new Date(health.lastAdvancedAt!).getTime(); + const now = Date.now(); + const elapsed = now - lastAdvancedTime; + const remaining = DEFAULT_TICK_INTERVAL_MS - (elapsed % DEFAULT_TICK_INTERVAL_MS); + setSecondsRemaining(Math.ceil(remaining / 1000)); + }; + + updateCountdown(); + const interval = setInterval(updateCountdown, 1000); + + return () => clearInterval(interval); + }, [health?.lastAdvancedAt, health?.currentTick]); + + if (secondsRemaining === null) { + return null; + } + + const helpText = `Time progression: The simulation advances in discrete weeks (ticks). Each week represents ${DEFAULT_TICK_INTERVAL_MS / 1000} seconds of real time. Production jobs, research, and shipments complete when the required number of weeks pass.`; + + return ( +
+ + + Next week in {secondsRemaining}s + + +
+ ); +} diff --git a/apps/web/src/components/layout/top-bar.tsx b/apps/web/src/components/layout/top-bar.tsx index 695cd3d3..d86e9101 100644 --- a/apps/web/src/components/layout/top-bar.tsx +++ b/apps/web/src/components/layout/top-bar.tsx @@ -15,6 +15,7 @@ import { useControlManager } from "./control-manager"; import { PROFILE_PANEL_ID } from "./profile-panel"; import { StatusIndicator } from "./status-indicator"; import { UiSfxSettings } from "./ui-sfx-settings"; +import { TickCountdown } from "./tick-countdown"; import { useWorldHealth } from "./world-health-provider"; export function TopBar() { @@ -38,7 +39,10 @@ export function TopBar() {

{TOP_BAR_TITLES[pathname] ?? "CorpSim"}

-

{formatCadencePoint(health?.currentTick)}

+
+

{formatCadencePoint(health?.currentTick)}

+ +
diff --git a/apps/web/src/components/market/order-placement-card.tsx b/apps/web/src/components/market/order-placement-card.tsx index 1fe6053d..8f74b7a8 100644 --- a/apps/web/src/components/market/order-placement-card.tsx +++ b/apps/web/src/components/market/order-placement-card.tsx @@ -30,8 +30,8 @@ export function OrderPlacementCard({ const [side, setSide] = useState<"BUY" | "SELL">("BUY"); const [selectedItemId, setSelectedItemId] = useState(""); const [itemSearch, setItemSearch] = useState(""); - const [priceInput, setPriceInput] = useState("1.00"); - const [quantityInput, setQuantityInput] = useState("1"); + const [priceInput, setPriceInput] = useState(""); + const [quantityInput, setQuantityInput] = useState(""); const [error, setError] = useState(null); const deferredItemSearch = useDeferredValue(itemSearch); @@ -144,7 +144,7 @@ export function OrderPlacementCard({ setQuantityInput(event.target.value)} - placeholder="1" + placeholder="Enter quantity (e.g., 100)" />
@@ -181,7 +181,7 @@ export function OrderPlacementCard({ setPriceInput(event.target.value)} - placeholder="1.00" + placeholder="Enter price (e.g., 1.50)" />

Enter dollars (for example, 0.80). The order is stored in cents. diff --git a/apps/web/src/components/production/production-page.tsx b/apps/web/src/components/production/production-page.tsx index 943033fd..a2f07808 100644 --- a/apps/web/src/components/production/production-page.tsx +++ b/apps/web/src/components/production/production-page.tsx @@ -78,7 +78,7 @@ export function ProductionPage() { const [recipePage, setRecipePage] = useState(1); const [recipePageSize, setRecipePageSize] = useState<(typeof PRODUCTION_RECIPE_PAGE_SIZE_OPTIONS)[number]>(10); - const [quantityInput, setQuantityInput] = useState("1"); + const [quantityInput, setQuantityInput] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoadingRecipes, setIsLoadingRecipes] = useState(true); const [isLoadingJobs, setIsLoadingJobs] = useState(true); @@ -614,7 +614,7 @@ export function ProductionPage() { setQuantityInput(event.target.value)} - placeholder="1" + placeholder="Enter number of runs (e.g., 10)" /> diff --git a/apps/web/src/components/workforce/workforce-page.tsx b/apps/web/src/components/workforce/workforce-page.tsx index ffe3dce0..017e9019 100644 --- a/apps/web/src/components/workforce/workforce-page.tsx +++ b/apps/web/src/components/workforce/workforce-page.tsx @@ -57,7 +57,7 @@ export function WorkforcePage() { logisticsPct: "20", corporatePct: "20" }); - const [capacityDeltaInput, setCapacityDeltaInput] = useState("0"); + const [capacityDeltaInput, setCapacityDeltaInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const [hasLoadedWorkforce, setHasLoadedWorkforce] = useState(false); const [isSavingAllocation, setIsSavingAllocation] = useState(false); @@ -246,6 +246,9 @@ export function WorkforcePage() { Organizational Capacity +

+ Workforce capacity determines production speed and research efficiency. Higher capacity allows faster operations, but increases weekly salary costs. Allocation percentages control which departments receive speed bonuses. +

@@ -278,42 +281,65 @@ export function WorkforcePage() { Allocation Controls +

+ Distribute your workforce across departments. Higher allocation in each area provides speed bonuses. Total must equal 100%. +

- - setAllocationDraft((current) => ({ ...current, operationsPct: event.target.value })) - } - placeholder="Operations %" - inputMode="numeric" - /> - - setAllocationDraft((current) => ({ ...current, researchPct: event.target.value })) - } - placeholder="Research %" - inputMode="numeric" - /> - - setAllocationDraft((current) => ({ ...current, logisticsPct: event.target.value })) - } - placeholder="Logistics %" - inputMode="numeric" - /> - - setAllocationDraft((current) => ({ ...current, corporatePct: event.target.value })) - } - placeholder="Corporate %" - inputMode="numeric" - /> -
@@ -345,7 +371,7 @@ export function WorkforcePage() { setCapacityDeltaInput(event.target.value)} - placeholder="Delta capacity" + placeholder="Enter change (e.g., +50 or -20)" inputMode="numeric" />