Conversation
useActivityTimer had one browser-only dependency: a beforeunload listener warning the user before closing the tab mid-activity. Move it into a new useUnloadWarning(active) hook and call it from GameContext, the component that owns the timer, so the timer hook itself has zero window references and stays platform-neutral. Fixes #572
useEntitySearchInput reached into the DOM directly for click-outside dismissal and typed its keydown handler against KeyboardEvent<HTMLInputElement>. This splits it along the platform boundary, per #573: - Hook now exposes intent-shaped actions (onSelectNext, onSelectPrevious, onDismiss, onCommit) instead of a DOM-typed handleKeyDown. - The document mousedown listener for click-outside dismissal moves to EntitySearchInput.tsx, which owns the rootRef and translates the gesture into onDismiss(). - EntitySearchInput.tsx's handleKeyDown translates raw KeyboardEvents into the hook's semantic actions. - window.setTimeout/window.clearTimeout -> bare setTimeout/clearTimeout. Fixes #573
…to a pure util ActivityRewardScreen computed the post-activity reward breakdown — premium bonus, task bonus, total XP, and the four summary-line phrasings — inline in its render path. Extracts that into a pure buildActivityRewardBreakdown() function in utils/, matching the existing buildActivityRewardToastMessage precedent. - Added frontend/src/utils/activityRewardBreakdown.ts: takes the raw reward fields (activityName, xpGained, baseXp, xpMultiplier, taskXpMultiplier, levelUps, elapsedSeconds) and returns the breakdown rows, the premium-multiplier gate (isLikelyPremiumUser), and the assembled summary line. No React, no side effects. - ActivityRewardScreen.tsx now calls the util and just renders the result; the inline calc block is gone. - Added activityRewardBreakdown.test.ts covering zero XP, no multipliers, premium-only, task-only, both multipliers, all four summary phrasings, level-up normalization, and missing XP. - Deliberately not merged with buildActivityRewardToastMessage — documented why in the new file's doc comment (different output shapes, mostly-disjoint inputs). Fixes #575
…og config Unquoted "Severity: critical" etc. were parsed as YAML mappings instead of strings, breaking the generate-release-notes API with a 400 (config schema mismatch on changelog.categories[1].labels).
useActivityTimer had one browser-only dependency: a beforeunload listener warning the user before closing the tab mid-activity. Move it into a new useUnloadWarning(active) hook and call it from GameContext, the component that owns the timer, so the timer hook itself has zero window references and stays platform-neutral. Fixes #572
useEntitySearchInput reached into the DOM directly for click-outside dismissal and typed its keydown handler against KeyboardEvent<HTMLInputElement>. This splits it along the platform boundary, per #573: - Hook now exposes intent-shaped actions (onSelectNext, onSelectPrevious, onDismiss, onCommit) instead of a DOM-typed handleKeyDown. - The document mousedown listener for click-outside dismissal moves to EntitySearchInput.tsx, which owns the rootRef and translates the gesture into onDismiss(). - EntitySearchInput.tsx's handleKeyDown translates raw KeyboardEvents into the hook's semantic actions. - window.setTimeout/window.clearTimeout -> bare setTimeout/clearTimeout. Fixes #573
…ue-575-implementation Resolves the ActivityRewardScreen.tsx conflict in favor of the base branch's RewardBreakdown component extraction, which supersedes this branch's buildActivityRewardBreakdown util (same calc logic, more broadly reused — also by the unified timer's Results state). Removes the now-redundant util and its test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSwWkchRGye37hoCmEfDCz
Adds a workflow that fires on release:published and sends an embed (title, notes, link) to a Discord channel via a webhook stored in the DISCORD_RELEASE_WEBHOOK_URL secret. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSwWkchRGye37hoCmEfDCz
Full pip-compile --upgrade run on requirements.in/dev-requirements.in (patch/minor bumps only, e.g. django 5.2.16->5.2.17, cryptography 49.0.0->50.0.0), plus nanoid 3.3.16->3.3.18 in the frontend lockfile. Fixes the 3 open high-severity Dependabot alerts (cryptography Bleichenbacher oracle, nanoid infinite-loop bug). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSwWkchRGye37hoCmEfDCz
npm update across the frontend workspace (Storybook 10.5.6->10.5.7, eslint, vite, maplibre-gl, axe-core, etc. — patch/minor only). Packages with available major bumps (typescript 7, jsdom 30, framer-motion 13, etc.) were left as-is pending separate review. Lint and unit tests (524 tests) pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSwWkchRGye37hoCmEfDCz
…ntain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
development → staging
useActivityTimer had one browser-only dependency: a beforeunload listener warning the user before closing the tab mid-activity. Move it into a new useUnloadWarning(active) hook and call it from GameContext, the component that owns the timer, so the timer hook itself has zero window references and stays platform-neutral. Fixes #572
Extract beforeunload guard out of useActivityTimer
useEntitySearchInput reached into the DOM directly for click-outside dismissal and typed its keydown handler against KeyboardEvent<HTMLInputElement>. This splits it along the platform boundary, per #573: - Hook now exposes intent-shaped actions (onSelectNext, onSelectPrevious, onDismiss, onCommit) instead of a DOM-typed handleKeyDown. - The document mousedown listener for click-outside dismissal moves to EntitySearchInput.tsx, which owns the rootRef and translates the gesture into onDismiss(). - EntitySearchInput.tsx's handleKeyDown translates raw KeyboardEvents into the hook's semantic actions. - window.setTimeout/window.clearTimeout -> bare setTimeout/clearTimeout. Fixes #573
…ion-wrgq0t Move DOM event handling out of useEntitySearchInput into its component
Extract XP/multiplier reward breakdown out of ActivityRewardScreen into a pure util
api/auth.ts was the only consumer of axios; the rest of api/ already
uses fetch via apiFetch. Ports it over and removes the dependency.
- api/auth.ts's loginUser now calls apiFetch("/auth/jwt/create/", ...)
instead of the axios instance in api/axios.ts (deleted).
- apiFetch gains a skipAuth option: skips both the Authorization
header and the getValidAccessToken() refresh path, for endpoints
that are unauthenticated by design. A 401 on a skipAuth call is
treated as "credentials rejected", not "session died" — it does not
call handleUnauthorized() or clear storage, unlike a normal 401.
loginUser passes skipAuth: true, since there's no session yet to
refresh and a failed login shouldn't log out whatever's already
stored.
- axios removed from package.json/package-lock.json.
- Added tests: api.test.ts covers skipAuth (no Authorization header,
no handler/refresh invoked, explicitAccessToken is still ignored
under skipAuth); auth.test.ts covers loginUser's call shape and
error passthrough.
withCredentials determination: NOT load-bearing. DRF's
DEFAULT_AUTHENTICATION_CLASSES only has JWTAuthentication —
SessionAuthentication is commented out (progress_rpg/settings/base.py)
— so no endpoint reads the session cookie for auth; the JWT bearer
token is what actually authenticates every request. No
credentials: 'include' equivalent was needed on apiFetch.
Note for reviewers: api/auth.ts's loginUser was already dead code
before this change — grepping the frontend turns up no callers. The
app's actual login/register/password-reset flows (useLogin.ts,
useRegister.ts, usePasswordReset.ts) call fetch directly and were
never on axios, so they're unaffected by this change and out of
scope here; wiring them onto this module would be a separate,
larger change.
Fixes #576
Consolidate onto a single HTTP stack: migrate api/auth.ts off axios
Introduces a capability abstraction so a building can hold multiple production activities (e.g. milling and baking) instead of exactly one via building_type. Additive only: BuildingCapability rows are backfilled from existing building_type values, and no callers (capacity_services, tasks.py, economy_status) are changed yet - step 1 of .claude/plans/building-capabilities-plan.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
…lity find_mill/find_bakery and population_capacity_report's mills/bakeries querysets now filter by capabilities__activity instead of building_type, so a building with multiple capabilities (e.g. a communal building that both mills and bakes) is counted for every relevant role. Test fixtures that create mill/bakery buildings now also create the matching BuildingCapability row. Step 2 of .claude/plans/building-capabilities-plan.md; economy_status output is unchanged. find_granary and field_shelter lookups stay on building_type (deliberately excluded from capabilities, per the plan). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
…ate by activity advance_mill_economy_tick/advance_bakery_economy_tick now iterate buildings by capabilities__activity instead of building_type, so a building with multiple capabilities (e.g. a communal building that both mills and bakes) gets ticked for each one. GoodsConversionState moves from a per-building OneToOneField to a per-(building, activity) ForeignKey + unique constraint, with a backfill migration mapping existing rows by their building's building_type. This is required alongside the tick change: with a single per-building flag, the first tick to run today would mark the building "processed" and the second would silently skip - see .claude/plans/building-capabilities-plan.md's Edge Cases. Added a regression test (MultiCapabilityBuildingTests) covering exactly this: a communal building with both capabilities produces bread on the same day, which is only possible if both ticks ran. economy_status now prints every conversion state on a building instead of assuming one. Step 3 of the plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
…lan) Sums link_points across every player link a character has ever had (active and historical), mirroring the existing Player.total_link_points. This will drive link-scaled worker productivity in a later step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
worker_capacity_present() weights each present worker by 1 + capped(total_link_points / SCALE), added alongside workers_present rather than replacing it, and wired into the three economy conversion ticks plus population_capacity_report. Unlinked NPCs still contribute exactly 1, so existing behavior is unchanged until a character is actually linked and played. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Wire population_estimation.starting_population() into planning_services.settlement_plan() at the end of both generation paths (watabou_import.import_watabou_village and spawn_villages), logging the recommended granary/milling/baking/farming building counts. Compute-and-log only - doesn't change which buildings get created yet, so the recommended numbers can be validated against real village files before generation behavior changes in a later step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Renames the management command file/tests and every call site (call_command, imports, the spawn_villages_task Celery task) plus comments and docs referencing the old name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Feat/building capabilities
…lan) Sums link_points across every player link a character has ever had (active and historical), mirroring the existing Player.total_link_points. This will drive link-scaled worker productivity in a later step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
worker_capacity_present() weights each present worker by 1 + capped(total_link_points / SCALE), added alongside workers_present rather than replacing it, and wired into the three economy conversion ticks plus population_capacity_report. Unlinked NPCs still contribute exactly 1, so existing behavior is unchanged until a character is actually linked and played. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Wire population_estimation.starting_population() into planning_services.settlement_plan() at the end of both generation paths (watabou_import.import_watabou_village and spawn_villages), logging the recommended granary/milling/baking/farming building counts. Compute-and-log only - doesn't change which buildings get created yet, so the recommended numbers can be validated against real village files before generation behavior changes in a later step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Renames the management command file/tests and every call site (call_command, imports, the spawn_villages_task Celery task) plus comments and docs referencing the old name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Feat/village capacity sizing
…gressrpg/ProgressRPG into feat/village_capacity_sizing
…d update tests accordingly
Farming/milling/baking buildings are now staffed against settlement_plan's workers_needed estimate instead of a flat random 2-3 per building, greedily filling one building to MAX_WORKERS_PER_BUILDING before moving to the next of the same role - a village with too few eligible working-age residents ends up understaffed rather than evenly thin everywhere, which is what actually makes the "struggling at spawn" arc real. A building holding multiple capabilities (e.g. a packed "communal" building) is staffed to the larger of its activities' demand, since a present worker counts fully toward every activity their building holds at once. Granary is excluded from worker assignment entirely - no economy tick reads its worker presence. Every other work building type (inn, market, hall, ...) keeps the original flat random assignment, since there's no demand model for those roles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
SettlementPlan gains combine_milling_and_baking: true whenever resident_count is at or below the new SMALL_SETTLEMENT_POPULATION_THRESHOLD (30). watabou_import now shares one "communal" building for milling and baking whenever a settlement is small, even if there'd be enough building slots for two dedicated ones - two half-empty production buildings isn't a better outcome than one shared one for a small village. Falls back to sharing regardless of population whenever there genuinely isn't room for two dedicated buildings, same as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxvVvCwZw9HrxFcspgbmSK
Grain and flour figures stay in kg indefinitely otherwise, which gets unreadable once a granary/mill is dealing in thousands of kg. Bread stays loaf-counted regardless of size, and signed deltas stay in plain kg - only unsigned wheat/flour quantities/rates over 1000kg switch.
Storage and interval-report sections listed one column/row per building, which gets unreadably wide once a world has more than a couple of villages - both now aggregate per population centre instead. Also fix the verdict's "went unfed" check: it was based on peak hunger value observed, which can include hunger a character already had entering the run (e.g. from the real Celery beat schedule ticking this same economy in the background) rather than anything caused during the simulated window. Now based on whether any character actually missed a meal on a simulated day.
Village capacity sizing: link-scaled productivity, plan-driven capabilities, demand-aware worker assignment
…ooltip anchoring and selection highlighting
…d hover affordance Fades the selected-building/character outline in via a paint-opacity transition instead of snapping instantly, swaps the hardcoded #ffb703 for the semantic accent color, and adds a dimmer hover-only preview ring on buildings/characters. Also fixes the hover rings rendering at full opacity instead of their intended dimmer resting state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a `compact` prop to the shared List component (tighter item padding/gap) and applies it to BuildingDetail's residents/workers lists, which are nested inside an already-dense detail panel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
…l character tooltips - A character standing inside a building was triggering both the building's and the character's hover outline at once, since MapLibre fires per-layer mousemove handlers independently for overlapping features. The buildings-fill hover handler now checks whether the same point also hits the "characters" layer and defers to it, mirroring the existing click-priority pattern. - Character map tooltips now show a single status line - "[Activity] at [Building]", "[Activity] outside", "Walking to [Building]", or "Walking outside" - instead of separate Currently/Lives at/Works at lines. - CharacterPointFeatureSerializer exposes current_location_type and destination_location_type, resolving through Node.interior_space as well as Node.building so characters in interior rooms (not just at a building's entrance node) resolve to that building instead of reading as "outside". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
Show "Currently" above Age/Sex in CharacterDetail, and reduce the non-modal DetailCard variant's max-width from 280px to 200px. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
Also read "home" instead of "house" when the character is at or walking to their own residence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
Cursor is inherited, so without an explicit default, hovering any plain text (tooltip copy, list items, etc.) showed the browser's text-selection I-beam. Sets cursor: default on body and restores cursor: text only on actual text inputs/textareas/contenteditable elements. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
…ails button Header now has a target-icon button (secondary variant, same size as Close) that recenters the map on whichever entity is selected. Also fixes Button's icon prop rendering unsized (no CSS constrained its SVG), and adds a small Button size used for the tooltip's View details action.
…e 768px Most body text read as too large on wider viewports; drop the md/lg font-size escalation from the shared type scale so text stays the same size at every breakpoint instead of growing past 768px.
Extracts MapDetailCard from DetailCard so the docked panel portals into Map's own wrapper (via a new DetailSurface `container` prop) and positions relative to the map instead of the viewport, fixing it sitting far too high on the page. It stays docked top-right through a wider range of narrow viewports before falling back to the mobile bottom sheet, and sits closer to the zoom controls. DetailCard itself is now always a centered modal. Also gives the character/building lists in the detail card a stronger hover effect (List's existing background/border hover rule was nearly invisible). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H8vdMQa7b5tHiz32seC6v
…-polish Feat/map selection highlight polish
…'s hours generate_day hardcoded a fixed 8:00-17:00 work window regardless of the character's actual work building, while movement (target_role_for) already read the building's real open_time/close_time. For a late-closing building like the inn (06:00-23:00), this meant a worker was still physically at the inn well into the evening but their scheduled activity had already fallen through to the fixed leisure block, showing "Relaxing" instead of a work activity. Extracted the building-hours lookup into a shared work_hours_for helper and use it to size the work blocks (and push dinner/leisure/wind-down after work actually ends) in generate_day too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…vity Fix character activity schedule to respect the assigned work building…
Development
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release summary
Features
Fixes and UX improvements
Developer experience and quality
spawn_villagestogenerate_villagesand routed mill/bakery lookups through the newBuildingCapabilitymodel for clearer economic modeling.Usermodel creation for theuser_factory.Technical notes
economy/migrations/0004_buildingcapability.py(addsBuildingCapabilitymodel),0005_backfill_building_capabilities.py,0006_goodsconversionstate_activity.py,0007_backfill_conversion_state_activity.py,0008_alter_goodsconversionstate_activity.py.DISCORD_RELEASE_WEBHOOK_URLsecret (already configured).Test plan