Skip to content

Feat/adventurer map#117

Merged
skuhlmann merged 4 commits into
stagingfrom
feat/adventurer-map
Jun 20, 2026
Merged

Feat/adventurer map#117
skuhlmann merged 4 commits into
stagingfrom
feat/adventurer-map

Conversation

@skuhlmann

@skuhlmann skuhlmann commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add authenticated /dashboard/map as an alternate Portal dashboard view.
  • Build the interactive map client with role-based character selection, sprite rendering, path/node movement, destination markers, accessible dialogs, and reduced-motion snapping.
  • Add map location dialogs backed by current Portal data: posts, modules, wiki pages, events, feedback copy, point totals, daily vibe check, and leaderboard.
  • Add GET /api/portal/leaderboard/points for a display-safe authenticated point leaderboard.
  • Add dashboard-map PageCopy support, fallback copy, seeds, migration, and Payload type update.
  • Add Playwright coverage for anonymous redirect, member character selection, map travel, and leaderboard response.
  • Update the map dashboard spec status to reflect the implemented first slice.

Summary by CodeRabbit

Release Notes

  • New Features

    • Interactive /dashboard/map dashboard featuring character role selection with animated sprites and location-based exploration with contextual dialogs
    • Points-based leaderboard system for tracking user achievements
  • Documentation

    • Added comprehensive map dashboard feature specifications

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@skuhlmann, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 59 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f74f48a2-350d-48ae-9fc5-2687d44435a7

📥 Commits

Reviewing files that changed from the base of the PR and between 56679a6 and b3566f3.

📒 Files selected for processing (5)
  • docs/feature-spec-index.md
  • public/assets/map/sprites/characters/ranger.json
  • src/app/(frontend)/dashboard/map/MapDashboardClient.tsx
  • src/app/(frontend)/dashboard/map/mapData.ts
  • tests/e2e/app.spec.ts
📝 Walkthrough

Walkthrough

Adds an interactive /dashboard/map authenticated dashboard: a feature spec, a DB migration extending the PageCopy surface enum, 16 character sprite JSON manifests, server-side data loaders, a leaderboard API endpoint, and a full set of client React components (dialog, sprite, movement hook, character selector, location dialogs, main client). A Next.js page entry point and E2E test coverage are included.

Changes

Interactive Map Dashboard

Layer / File(s) Summary
Feature spec and index entry
docs/feature-spec-index.md, docs/map-dashboard-feature-spec.md
Adds a 799-line feature specification covering v1 scope, asset requirements, data contracts, movement model, dialog content, accessibility requirements, implementation phases, and open questions. Registers it in the spec index.
DB migration, PageCopy surface enum, and CMS wiring
src/migrations/20260620_120000_dashboard_map_page_copy.ts, src/migrations/index.ts, src/payload-types.ts, src/collections/PageCopy.ts, src/utilities/pageCopy.ts, src/endpoints/seed/index.ts, src/endpoints/seed/portal.ts
Extends enum_page_copy_surface with 'dashboard' (up/down migration), registers the migration, adds 'dashboard' to the PageCopy collection select options and pathForPageCopyKey mapping, updates the PageCopy.surface TypeScript union, and adds dashboardMapPageFallback, getDashboardMapPageCopy, and seeded dashboard-map pageCopy records.
Map graph config, types, and server data loader
src/app/(frontend)/dashboard/map/mapConfig.ts, src/app/(frontend)/dashboard/map/mapData.ts
Defines MapLocationID, MapNodeID, coordinate/node/location types, SpriteDirection, sprite alias/allowlist constants, the mapNodes adjacency graph, mapLocations, and BFS findPath. Adds MapDashboardData and SelectableMapRole types plus getMapDashboardData with parallel helpers for profile, posts, modules, wiki, events, points, daily engagement streak, and role-to-sprite mapping.
Leaderboard points API route
src/app/(frontend)/api/portal/leaderboard/points/route.ts
Implements GET /api/portal/leaderboard/points: verifies authentication and account status (401/403), aggregates pointEvents per recipient, filters by profile visibility with a current-user exception, sorts and slices to top 10, and returns ranked entries with generatedAt.
Character sprite manifests
public/assets/map/sprites/characters/*.json
Adds 16 uniform sprite metadata JSON files (alchemist, archer, cleric, druid, dwarf, healer, hunter, monk, necromancer, paladin, ranger, rogue, scribe, tavern-keeper, warrior, wizard), each specifying 54×68 cell size, animation/state name list, and source pose-sheet path.
MapDialog accessibility component and MapSprite renderer
src/app/(frontend)/dashboard/map/MapDialog.tsx, src/app/(frontend)/dashboard/map/MapSprite.tsx
MapDialog implements a modal with ARIA attributes, Escape/Tab focus-trap keydown handling, auto-focus to first focusable element, and focus restoration on unmount. MapSprite renders a positioned button with CSS backgroundPosition derived from direction and spriteSlug.
useMapMovement hook and MapCharacterSelector
src/app/(frontend)/dashboard/map/useMapMovement.ts, src/app/(frontend)/dashboard/map/MapCharacterSelector.tsx
useMapMovement tracks node position, direction, and movement state; animates frame-by-frame via requestAnimationFrame along BFS paths; snaps immediately when prefers-reduced-motion is set. MapCharacterSelector renders a MapDialog with profile identity, guild points, daily streak, a role button grid (disabled for unavailable sprites), and fallback states for missing profile or no available art.
MapLocationDialog, MapDashboardClient, and page entry point
src/app/(frontend)/dashboard/map/MapLocationDialog.tsx, src/app/(frontend)/dashboard/map/MapDashboardClient.tsx, src/app/(frontend)/dashboard/map/page.tsx
MapLocationDialog switch-dispatches per-location dialogs (post, modules, wiki, events+calendar, leaderboard fetch from API, CMS copy). MapDashboardClient reads/persists role selection from localStorage, wires travel to useMapMovement.travelTo, renders the map background with positioned buttons, MapSprite, and status overlay, and gates modals for character selection and location dialogs. page.tsx enforces auth/verification gates, loads MapDashboardData, and renders the client.
E2E test coverage
tests/e2e/app.spec.ts
Adds verifyMapDashboard: provisions a user/profile via admin API, logs in, asserts /dashboard/map character selector and warrior/slop-swamp dialog interactions, validates /api/portal/leaderboard/points response shape, and wires the helper into the main E2E sequence.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant DashboardMapPage as page.tsx
  participant getMapDashboardData
  participant MapDashboardClient
  participant useMapMovement
  participant MapLocationDialog
  participant LeaderboardAPI as GET /api/portal/leaderboard/points
  participant Payload

  Browser->>DashboardMapPage: GET /dashboard/map
  DashboardMapPage->>Payload: getCurrentUser()
  alt unauthenticated
    DashboardMapPage-->>Browser: redirect /join
  else unverified
    DashboardMapPage-->>Browser: VerifyAccountNotice
  end
  DashboardMapPage->>getMapDashboardData: fetch profile/posts/modules/wiki/events/points/streak
  getMapDashboardData-->>DashboardMapPage: MapDashboardData
  DashboardMapPage-->>Browser: render MapDashboardClient

  Browser->>MapDashboardClient: click map location button
  MapDashboardClient->>useMapMovement: travelTo(nodeID)
  useMapMovement-->>MapDashboardClient: onArrive → setActiveLocation
  MapDashboardClient-->>Browser: render MapLocationDialog (LunkerLake)
  MapLocationDialog->>LeaderboardAPI: fetch /api/portal/leaderboard/points
  LeaderboardAPI->>Payload: auth + query pointEvents + profiles
  Payload-->>LeaderboardAPI: events[], profiles[]
  LeaderboardAPI-->>MapLocationDialog: { entries, generatedAt }
  MapLocationDialog-->>Browser: render ranked leaderboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • raid-guild/portal#26: The new leaderboard and map page both gate access by emailVerifiedAt, which relies on the email verification token mechanism introduced in this PR.
  • raid-guild/portal#61: This PR directly extends the PageCopy CMS system (new dashboard surface, migration, seeds, utility functions) first established in the referenced PR.

Poem

🐇 Hop across the swamp, past the wizard's spire,
Choose your form — a druid, rogue, or friar.
BFS finds the path, the sprite starts to roam,
Each node a dialog, each click a new home.
The leaderboard glows at Lunker Lake's shore —
Ten champions ranked, and room for ten more! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Feat/adventurer map" is vague and generic, using unclear phrasing that doesn't clearly convey the specific feature being added. Revise to a more descriptive title that clearly indicates the main change, such as "Add interactive map dashboard" or "Implement /dashboard/map endpoint with character selection".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/adventurer-map

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/app/(frontend)/api/portal/leaderboard/points/route.ts (1)

30-41: Remove redundant limit parameter when using pagination: false.

In Payload CMS v3, setting pagination: false returns all documents regardless of the limit parameter. The limit: 10000 is dead code and could mislead future maintainers into thinking there's an upper bound on results.

♻️ Proposed fix
   const pointEvents = await payload.find({
     collection: 'pointEvents',
     depth: 0,
-    limit: 10000,
     overrideAccess: true,
     pagination: false,
     where: {
       status: {
         equals: 'valid',
       },
     },
   })
   const profiles = await payload.find({
     collection: 'profiles',
     depth: 1,
-    limit: 10000,
     overrideAccess: true,
     pagination: false,
     where: {

Also applies to: 53-58

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(frontend)/api/portal/leaderboard/points/route.ts around lines 30 -
41, Remove the redundant limit parameter from the payload.find() calls where
pagination is set to false. In Payload CMS v3, when pagination is false, all
documents are returned regardless of the limit value, making the limit parameter
unnecessary and potentially misleading. Search for all instances of
payload.find() calls in the leaderboard points route that have both pagination:
false and a limit parameter (this occurs in the main query around the
pointEvents collection and also in the additional location mentioned at lines
53-58), and delete the limit property from each of these calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/feature-spec-index.md`:
- Line 41: In the feature-spec-index.md file, locate the "Interactive map
dashboard" row entry that is currently marked with status "Planned / future" and
move it from its current location to the "Implemented First Slices" section.
Update the status column for this entry from "Planned / future" to "First slice
implemented" to accurately reflect that this PR delivers the first implemented
slice of the feature.

In `@public/assets/map/sprites/characters/ranger.json`:
- Line 18: The source path in the ranger.json manifest file is missing the
generated-poses directory and does not follow the consistent pattern used in
other character manifests. Update the source field value from
assets-src/art-review/ranger-pose-sheet-v1-chroma.png to
assets-src/art-review/generated-poses/ranger-pose-sheet-v1-chroma.png to match
the standard pattern where the generated-poses directory is included in the path
for all character pose sheet sources.

In `@src/app/`(frontend)/dashboard/map/MapDashboardClient.tsx:
- Around line 63-70: The travelToLocation function currently allows travel to
any location that passes the selectedRole check, but it does not respect the
disabled state of locations. Add a check in the travelToLocation function to
return early if location.disabled is true, preventing users from traveling to or
interacting with disabled destinations. Place this check after or alongside the
existing selectedRole validation to ensure disabled locations are not accessible
regardless of the styling applied to them.

In `@src/app/`(frontend)/dashboard/map/mapData.ts:
- Line 82: The leaderboard field is being hard-coded as an empty array on line
82, which causes it to display a false "no standings" state. Instead of using an
empty array, extract the actual leaderboard data from the server payload
response and assign it to the leaderboard field. This will ensure that when data
is fetched from the server, the actual standings are properly displayed rather
than showing an empty state.

In `@tests/e2e/app.spec.ts`:
- Around line 297-323: The test function creates a BrowserContext with
browser.newContext() but if any expect() assertion fails before reaching
context.close(), the context leaks because the cleanup code is never executed.
Wrap all the test operations between creating the context and calling
context.close() in a try block, then move the context.close() call into a
finally block to guarantee the context is always closed regardless of whether
assertions pass or fail.

---

Nitpick comments:
In `@src/app/`(frontend)/api/portal/leaderboard/points/route.ts:
- Around line 30-41: Remove the redundant limit parameter from the
payload.find() calls where pagination is set to false. In Payload CMS v3, when
pagination is false, all documents are returned regardless of the limit value,
making the limit parameter unnecessary and potentially misleading. Search for
all instances of payload.find() calls in the leaderboard points route that have
both pagination: false and a limit parameter (this occurs in the main query
around the pointEvents collection and also in the additional location mentioned
at lines 53-58), and delete the limit property from each of these calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27f0c2f3-daeb-4fe9-90ee-6a550ffd2cae

📥 Commits

Reviewing files that changed from the base of the PR and between 7b01be0 and 56679a6.

⛔ Files ignored due to path filters (32)
  • public/assets/map/characters/alchemist.svg is excluded by !**/*.svg
  • public/assets/map/characters/archer.svg is excluded by !**/*.svg
  • public/assets/map/characters/cleric.svg is excluded by !**/*.svg
  • public/assets/map/characters/druid.svg is excluded by !**/*.svg
  • public/assets/map/characters/dwarf.svg is excluded by !**/*.svg
  • public/assets/map/characters/healer.svg is excluded by !**/*.svg
  • public/assets/map/characters/hunter.svg is excluded by !**/*.svg
  • public/assets/map/characters/monk.svg is excluded by !**/*.svg
  • public/assets/map/characters/necromancer.svg is excluded by !**/*.svg
  • public/assets/map/characters/paladin.svg is excluded by !**/*.svg
  • public/assets/map/characters/ranger.svg is excluded by !**/*.svg
  • public/assets/map/characters/rogue.svg is excluded by !**/*.svg
  • public/assets/map/characters/scribe.svg is excluded by !**/*.svg
  • public/assets/map/characters/tavern-keeper.svg is excluded by !**/*.svg
  • public/assets/map/characters/warrior.svg is excluded by !**/*.svg
  • public/assets/map/characters/wizard.svg is excluded by !**/*.svg
  • public/assets/map/sprites/characters/alchemist.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/archer.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/cleric.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/druid.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/dwarf.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/healer.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/hunter.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/monk.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/necromancer.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/paladin.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/ranger.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/rogue.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/scribe.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/tavern-keeper.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/warrior.png is excluded by !**/*.png
  • public/assets/map/sprites/characters/wizard.png is excluded by !**/*.png
📒 Files selected for processing (37)
  • docs/feature-spec-index.md
  • docs/map-dashboard-feature-spec.md
  • public/assets/map/backgrounds/adventure-map-background.webp
  • public/assets/map/sprites/characters/alchemist.json
  • public/assets/map/sprites/characters/archer.json
  • public/assets/map/sprites/characters/cleric.json
  • public/assets/map/sprites/characters/druid.json
  • public/assets/map/sprites/characters/dwarf.json
  • public/assets/map/sprites/characters/healer.json
  • public/assets/map/sprites/characters/hunter.json
  • public/assets/map/sprites/characters/monk.json
  • public/assets/map/sprites/characters/necromancer.json
  • public/assets/map/sprites/characters/paladin.json
  • public/assets/map/sprites/characters/ranger.json
  • public/assets/map/sprites/characters/rogue.json
  • public/assets/map/sprites/characters/scribe.json
  • public/assets/map/sprites/characters/tavern-keeper.json
  • public/assets/map/sprites/characters/warrior.json
  • public/assets/map/sprites/characters/wizard.json
  • src/app/(frontend)/api/portal/leaderboard/points/route.ts
  • src/app/(frontend)/dashboard/map/MapCharacterSelector.tsx
  • src/app/(frontend)/dashboard/map/MapDashboardClient.tsx
  • src/app/(frontend)/dashboard/map/MapDialog.tsx
  • src/app/(frontend)/dashboard/map/MapLocationDialog.tsx
  • src/app/(frontend)/dashboard/map/MapSprite.tsx
  • src/app/(frontend)/dashboard/map/mapConfig.ts
  • src/app/(frontend)/dashboard/map/mapData.ts
  • src/app/(frontend)/dashboard/map/page.tsx
  • src/app/(frontend)/dashboard/map/useMapMovement.ts
  • src/collections/PageCopy.ts
  • src/endpoints/seed/index.ts
  • src/endpoints/seed/portal.ts
  • src/migrations/20260620_120000_dashboard_map_page_copy.ts
  • src/migrations/index.ts
  • src/payload-types.ts
  • src/utilities/pageCopy.ts
  • tests/e2e/app.spec.ts

Comment thread docs/feature-spec-index.md Outdated
Comment thread public/assets/map/sprites/characters/ranger.json Outdated
Comment thread src/app/(frontend)/dashboard/map/MapDashboardClient.tsx
Comment thread src/app/(frontend)/dashboard/map/mapData.ts Outdated
Comment thread tests/e2e/app.spec.ts
@skuhlmann

Copy link
Copy Markdown
Contributor Author

Moved Interactive map dashboard into Implemented First Slices and updated status in feature-spec-index.md

Fixed ranger manifest source path in ranger.json (line 18)

Blocked disabled map destinations in travelToLocation and disabled both map/destination buttons in MapDashboardClient.tsx (line 63)

Replaced the hard-coded empty leaderboard with server-loaded point standings in mapData.ts (line 58)

Wrapped the map dashboard e2e browser context in try/finally in app.spec.ts (line 297)

@dekanbro
dekanbro requested a review from Copilot June 20, 2026 23:20
@skuhlmann
skuhlmann merged commit f70f6e9 into staging Jun 20, 2026
2 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an alternate authenticated dashboard experience at /dashboard/map, backed by server-side map data loading and a display-safe points leaderboard API. This fits into the existing Portal primitives by reusing posts/modules/wiki/events/feedback/points rather than introducing new content models.

Changes:

  • Introduces /dashboard/map (server page + client map UI) with character selection, path/node travel, and location dialogs backed by Portal data.
  • Adds GET /api/portal/leaderboard/points for authenticated, verified users to fetch a top-10 points leaderboard with visibility filtering.
  • Extends PageCopy to support a new dashboard surface (dashboard-map key) with fallback copy, seeds, and a Postgres enum migration; adds Playwright coverage and feature spec docs.

Reviewed changes

Copilot reviewed 36 out of 69 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/e2e/app.spec.ts Adds e2e coverage for map dashboard auth redirect, character selection, travel/dialog open, and leaderboard endpoint response.
src/utilities/pageCopy.ts Adds dashboard-map fallback copy and a getter for map dashboard PageCopy.
src/payload-types.ts Expands PageCopy.surface union to include dashboard.
src/migrations/index.ts Registers a new migration for the PageCopy surface enum update.
src/migrations/20260620_120000_dashboard_map_page_copy.ts Adds Postgres enum value dashboard for PageCopy surface.
src/endpoints/seed/portal.ts Seeds a published dashboard-map PageCopy record (upsert-based portal seeding).
src/endpoints/seed/index.ts Seeds a published dashboard-map PageCopy record (create-based seed flow).
src/collections/PageCopy.ts Maps dashboard-map to /dashboard/map and adds dashboard as an admin surface option.
src/app/(frontend)/dashboard/map/useMapMovement.ts Implements node/path travel animation with reduced-motion snapping.
src/app/(frontend)/dashboard/map/page.tsx Adds the /dashboard/map route with auth + verified-email gating and server data load.
src/app/(frontend)/dashboard/map/MapSprite.tsx Renders the selected character sprite with direction-based frame selection.
src/app/(frontend)/dashboard/map/MapLocationDialog.tsx Adds location dialogs (posts/modules/wiki/events/feedback/points/leaderboard/vibe check).
src/app/(frontend)/dashboard/map/MapDialog.tsx Adds a custom accessible dialog shell with focus management and keyboard handling.
src/app/(frontend)/dashboard/map/mapData.ts Server-side loader for map dashboard data, including role-to-sprite selection and points/leaderboard summaries.
src/app/(frontend)/dashboard/map/MapDashboardClient.tsx Main interactive map UI: background, destination markers/menu, sprite placement, dialogs.
src/app/(frontend)/dashboard/map/mapConfig.ts Defines map nodes/links, destinations, sprite availability lists, and BFS pathfinding.
src/app/(frontend)/dashboard/map/MapCharacterSelector.tsx Role-based character selection dialog with unavailable-art handling and profile links.
src/app/(frontend)/api/portal/leaderboard/points/route.ts Implements authenticated “display-safe” points leaderboard API (top 10, visibility-filtered).
public/assets/map/sprites/characters/alchemist.json Adds sprite-sheet metadata for the alchemist character.
public/assets/map/sprites/characters/archer.json Adds sprite-sheet metadata for the archer character.
public/assets/map/sprites/characters/cleric.json Adds sprite-sheet metadata for the cleric character.
public/assets/map/sprites/characters/druid.json Adds sprite-sheet metadata for the druid character.
public/assets/map/sprites/characters/dwarf.json Adds sprite-sheet metadata for the dwarf character.
public/assets/map/sprites/characters/healer.json Adds sprite-sheet metadata for the healer character.
public/assets/map/sprites/characters/hunter.json Adds sprite-sheet metadata for the hunter character.
public/assets/map/sprites/characters/monk.json Adds sprite-sheet metadata for the monk character.
public/assets/map/sprites/characters/necromancer.json Adds sprite-sheet metadata for the necromancer character.
public/assets/map/sprites/characters/paladin.json Adds sprite-sheet metadata for the paladin character.
public/assets/map/sprites/characters/ranger.json Adds sprite-sheet metadata for the ranger character.
public/assets/map/sprites/characters/rogue.json Adds sprite-sheet metadata for the rogue character.
public/assets/map/sprites/characters/scribe.json Adds sprite-sheet metadata for the scribe character.
public/assets/map/sprites/characters/tavern-keeper.json Adds sprite-sheet metadata for the tavern keeper character.
public/assets/map/sprites/characters/warrior.json Adds sprite-sheet metadata for the warrior character.
public/assets/map/sprites/characters/wizard.json Adds sprite-sheet metadata for the wizard character.
public/assets/map/characters/monk.svg Adds a static portrait asset for monk.
public/assets/map/characters/healer.svg Adds a static portrait asset for healer.
public/assets/map/characters/archer.svg Adds a static portrait asset for archer.
docs/map-dashboard-feature-spec.md Adds/updates the interactive map dashboard feature spec with status and implementation details.
docs/feature-spec-index.md Adds the interactive map dashboard to the feature spec index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/e2e/app.spec.ts
Comment on lines +301 to +305
await loginPortalUser(mapPage, email, password)
await mapPage.goto('/dashboard/map')
await expect(mapPage.getByRole('heading', { name: /walk the portal roads/i })).toBeVisible()
await expect(mapPage.getByRole('dialog', { name: /choose your guild form/i })).toBeVisible()

Comment on lines +77 to +83
document.addEventListener('keydown', onKeyDown)
window.setTimeout(focusFirst, 0)

return () => {
document.removeEventListener('keydown', onKeyDown)
previousActiveElement?.focus()
}
Comment on lines +233 to +262
const getPointSummary = async (user: User) => {
const payload = await getPayload({ config: configPromise })
const result = await payload.find({
collection: 'pointEvents',
depth: 1,
limit: 1000,
overrideAccess: false,
pagination: false,
sort: '-issuedAt',
user,
where: {
and: [
{
recipient: {
equals: user.id,
},
},
{
status: {
equals: 'valid',
},
},
],
},
})

return {
events: result.docs.slice(0, 5),
total: result.docs.reduce((sum, event) => sum + (event.amount || 0), 0),
}
Comment on lines +59 to +79
const [
copy,
profile,
latestPost,
prototypeModules,
recentWikiPages,
upcomingEvents,
pointSummary,
leaderboard,
dailyEngagementSummary,
] = await Promise.all([
getDashboardMapPageCopy(),
getProfileForUser(user.id),
getLatestPost(user),
getPrototypeModules(user),
getRecentWikiPages(user),
getUpcomingEvents(user),
getPointSummary(user),
getPointLeaderboard(user),
getDailyEngagementSummary(user),
])
Comment on lines +117 to +145
const aggregatePointTotals = (events: PointEvent[]) => {
const totals = new Map<string, number>()

events.forEach((event) => {
const recipientID = relationID(event.recipient)
if (!recipientID) return

totals.set(recipientID, (totals.get(recipientID) || 0) + (event.amount || 0))
})

return totals
}

const relationID = (value: unknown): null | string => {
if (typeof value === 'number' || typeof value === 'string') return String(value)
if (value && typeof value === 'object' && 'id' in value) {
const id = (value as { id?: number | string }).id
if (typeof id === 'number' || typeof id === 'string') return String(id)
}

return null
}

const mediaURL = (value: unknown): string | undefined => {
if (!value || typeof value !== 'object') return undefined
const url = (value as Media).url

return typeof url === 'string' ? url : undefined
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants