Valenar - Shell Screen Model
This page owns the shell-level UX contract for the Valenar client. It defines the map interaction contract, the surface taxonomy (tooltip, ContextPanel, shared Activity Detail inspector, FloatingSurface), the panel-mode vocabulary (compact / wide / full / fullscreen), the modal taxonomy, and the overlay layering rules. Every other UX doc resolves naming conflicts back here.
Core Rule
The map is the primary surface. Panels, screens, and dossiers should explain or control the currently relevant strategic state without breaking that map-first identity.
Shell Responsibilities
- global chrome and rail tabs
- compact, wide, full, and fullscreen panel behavior
- map selection and focus state
- tooltip, ContextPanel, shared Activity Detail inspector, FloatingSurface, and fullscreen ownership
- pointer-event routing (hover / click / double-click / right-click) from the canvas into panel and overlay state
- transitions into special-purpose surfaces such as combat or dungeon watch
Map Interaction Contract
The strategic map is the canonical interaction surface. The canvas owns the authoritative geometry (territory polygons, marker positions, route paths); the shell does not duplicate that geometry in SVG or DOM. Pointer events on the canvas resolve to a Territory id via point-in-polygon and to a Marker id via the canvas-owned spatial index.
The interaction contract is:
| Gesture | Effect |
|---|---|
| Hover on a Territory | Compact map tooltip via the shared data-tip-id tooltip engine. Read-only. |
| Hover on a Marker | Marker-scoped tooltip identifying the marker's underlying Feature, Site, Threat, clue, or activity hint. Read-only. |
| Left click on a Territory | Selects the Territory. Compact panel updates. If the Territory panel is in wide mode, the wide panel updates. |
| Double click on a Territory | Focuses and zooms the map on the Territory and opens the full-mode planning board (see Panel Mode Vocabulary, Full). |
| Right click on a Territory | Opens the anchored TerritoryContextPanel at the cursor with grouped quick activities. See ContextPanel. |
| Click on a cluster marker | Selects the owning Territory. Clusters render at the Territory centroid, so a cluster click resolves through the same point-in-polygon path as a direct Territory click and inherits Territory selection behavior. The cluster's underlying Feature, Site, Threat, clue, and activity-hint rows surface through the TerritoryContextPanel on right-click and through the Wide and Full Territory panels. No dedicated cluster tray ships in this slice; a future wave may add one if marker density makes the TerritoryContextPanel cluttered. |
| Click on an individual marker | Marker-scoped quick activities (Inspect, Open Feature / Site dossier, Add to queue, Pin). |
Pointer routing is implemented client-side in useMapPointerInteraction, which
uses a spatial grid to dedupe hover updates so React state only fires on tile
or polygon-boundary crossings. The tooltip anchor API is exposed additively
through openCanvasTerritoryTooltip in the tooltip resolvers; the tooltip
engine core remains untouchable per its existing token contract.
Markers are presentation-layer visualizations, not a new gameplay entity type. See ../systems/gd-map-markers.md for the marker taxonomy.
UI Surface Taxonomy
Four distinct shell surfaces coexist. They are not interchangeable. The Tooltip / ContextPanel / FloatingSurface split is the workspace-root ADR ad-0002-ui-surface-taxonomy.md; the shared Activity Detail inspector is a fourth surface category specified by this doc.
| Surface | Behavior | When to use |
|---|---|---|
| Tooltip | Read-only, hover-anchored, non-blocking, dismiss on pointer-leave or focus change. Driven by the data-tip-id registry and the shared tooltip engine. | Quick read-only inspection. Marker, channel, activity, journal, or queue row hover. |
| ContextPanel | Anchored popover at a pointer or element, non-blocking, dismissed on outside click, Escape, activity selection, or selection change. Implemented by TerritoryContextPanel for the map. | Right-click quick-activity menus on a Territory or individual marker. Grouped activity entry points that route through the canonical queue path. |
| Activity Detail | Nested drill-through inspector. Hosted inside the left overlay slot (the same slot the Activities panel uses), non-blocking — it does not dim the map. Carries description, costs and rewards, effect-plan preview, queue / cancel / reorder controls (server-backed), a blocked-reason chip, and a breadcrumb / back stack for recursive drill into related runs. Dismisses via explicit close, back navigation, or selection change. | Deep inspection of a specific activity definition or active run from any of the canonical openers (Queue, Territory Dossier Activities tab, Activity Catalog, Current Plan, Objectives, ContextPanel selection, BottomQueueSlots, Journal). Any entry point that needs more than a tooltip's read-only hover summary but must not block map interaction. |
| FloatingSurface | Centered or anchored modal, blocking, dismiss requires explicit confirm / cancel / close. Implements branching choice, dangerous confirmation, and major outcome flows. | Story or branching choice, dangerous activity confirmation, major discovery, ritual interaction, found camp, combat result, event resolution, route customization. |
The Tooltip surface is read-only by contract. It must not gain inline buttons, forms, or activity dispatch. Interactive popovers belong to ContextPanel.
The ContextPanel must not become a modal. It does not block input outside its anchor, it never dims the underlying map, and it dismisses on the next selection change. Dangerous or branching activities surface a FloatingSurface modal from the ContextPanel rather than performing the activity inline.
The Activity Detail surface must not become a modal and must not collapse into the Tooltip or ContextPanel. Tooltip is read-only and dismissed on pointer-leave; ContextPanel is a quick-activity menu dismissed on selection; Activity Detail is the persistent nested inspector that the player explicitly opens and closes. It is not a new screen, route, or shell mode — it is a structured state of the left overlay slot.
The FloatingSurface modal must not be used for routine repeated activities. See Modal Taxonomy.
Activity Detail
Activity Detail is the shared nested inspection layer for activities across all entry points. It is the fourth shell surface in the taxonomy alongside Tooltip, ContextPanel, and FloatingSurface. It does not introduce a separate screen, a separate router path, a separate shell mode, or a separate Activity workspace component; it is a structured nested state of the left overlay slot that hosts the Activities panel.
What Activity Detail owns
- Description, duration, stamina cost, resource costs, and rewards drawn from the activity definition.
- Effect-plan preview (server-backed predicted effects when the activity has a
Previewbody), surfaced through the same tooltip-resolver chain that produces hover read-back. - Queue / cancel / reorder controls that route through the canonical queue path — server-backed, same commands the Queue screen uses. Activity Detail does not locally start, advance, or complete activity runs.
- Blocked-reason chip when a blocked reason is available. The server-confirmed blocked-reason field that Wave B projects onto the availability read model is Wave B-owned, pending sync; until it ships, the chip is client-derived as documented in gd-queue-screen.md § Queue State Vocabulary.
- Opener context preserved as a breadcrumb / back trail: the source surface (Queue, Territory Dossier, Activity Catalog, Current Plan, Objectives, ContextPanel, BottomQueueSlots, Journal, or another Activity Detail when drilling recursively), the actor, the target territory or site, the queue item or run reference when applicable, the objective / mission / journal context when applicable, and the parent detail stack when the player drilled in from another Activity Detail.
- Recursive drill-stack: the player may drill from a run's Activity Detail into a related child-run or parent-run Activity Detail using the parent-run / child-run reference fields that Wave B exposes on the activity run snapshot (Wave B-owned, pending sync). Cycle and depth guards prevent infinite or runaway stacks.
What Activity Detail does NOT own
- Execution state. The Queue and the server remain the execution owner. Activity Detail reads from the queue read model and invokes existing queue commands; it never owns its own per-surface start / advance / complete path.
- Place context. Territory Dossier owns place facts; Activity Detail carries only the place context passed to it by the opener (territory id and the name label needed for display).
- Narrative readback. Journal owns durable structured memory; Activity Detail may link to a Journal entry but does not render Journal prose inline.
- Persistent memory. Current Plan summarizes the moment; Activity Detail is transient and dismisses when the player closes it or pops the entire stack.
- Per-surface duplicate implementations. There is exactly one shared Activity Detail surface; per-surface "mini detail panels" are a design FAIL and are resolved by routing through the shared inspector with opener context.
Opener taxonomy
Activity Detail is opened from the following canonical openers, each passing an opener-context payload. Backend field names for any run id, parent-run id, or child-run id referenced below are Wave B-owned, pending sync; the opener contract names them abstractly here.
| Opener | Opener-context shape (abstract) |
|---|---|
| Queue row (Running or Queued or blocked decoration) | source: 'queue', the queue item reference, the activity def id, the actor reference |
| Territory Dossier Activities tab row | source: 'dossier', the territory id, the activity def id |
| Activity Catalog row (in the Activities panel) | source: 'catalog', the activity def id |
| Current Plan activity link | source: 'plan', the activity def id, the associated objective reference when present |
| Objectives or Mission activity link | source: 'objectives', the activity def id, the objective reference, the mission reference when present |
| ContextPanel activity entry (tap activity name rather than the inline queue affordance) | source: 'context-panel', the territory id, the activity def id |
| BottomQueueSlots slot click | source: 'bottom-queue-slots', the queue item reference for a filled slot, or the activity def id for an empty-slot promotion |
| Journal activity link | source: 'journal', the activity def id, the journal entry reference |
| Recursive (Activity Detail → related run) | source: 'detail', the parent detail stack, the activity def id, the related run reference exposed by Wave B |
All run / parent-run / child-run reference field names in the opener-context payloads are Wave B-owned, pending sync. Wave M owner docs use abstract names ("the parent-run reference Wave B exposes", "the queue item reference") rather than locking specific DTO field names.
Recursive drill-stack semantics
"Routine" and "Composite" are planning-layer display and readback labels documented in ../systems/gd-objectives-clues-missions.md. They are NOT source-language keywords — neither a Routine block keyword nor a Composite block keyword exists in the committed SECS keyword list — and they are NOT a separate runtime executor type, a new planning noun above activity / policy, or a new content vocabulary. The recursive drill-stack is purely a read-model and UI display architecture built on the engine's existing activity run parent / child reference chain.
Read-model substrate. The engine's activity run type already carries a parent-run reference and a child-run reference. Wave B projects them onto the activity run snapshot consumed by the client. Until Wave B ships, every doc reference to these fields uses the abstract phrasing "the parent-run / child-run reference Wave B exposes — Wave B-owned, pending sync".
UI model. Activity Detail maintains an ordered drill-stack — the chain of opener contexts from the first opening at the bottom to the currently visible Activity Detail at the top. Each stack entry carries the activity def id, the display name used for the breadcrumb label, the source surface at that depth ('queue' / 'dossier' / 'catalog' / 'plan' / 'objectives' / 'context-panel' / 'bottom-queue-slots' / 'journal' for the root entry, and 'detail' for every recursive drill-in step), and the run reference when applicable (Wave B-owned, pending sync).
The drill-stack has:
- Arbitrary depth. There is no hard cap by design — the player may drill from a top-level activity run into its child run, into that child's child, and so on as long as the parent / child chain exists in the read model.
- Cycle guard. The inspector refuses to open a run that already appears in the current stack. If the engine were to produce a cycle in a run chain — it should not, but the UI defends against it anyway — the drill-in affordance for the already-present run is disabled.
- Depth guard. An implementation-defined maximum depth (suggested floor: sixteen) prevents runaway stacks if a buggy read model is received. Hitting the guard surfaces a non-blocking "maximum depth reached" notice at the deepest visible level. The guard is a defence, not the player-facing cap.
Breadcrumb and back navigation. The breadcrumb strip at the top of Activity Detail shows the current stack from left (oldest) to right (active): the source surface label as the leftmost crumb, the chain of drilled-into activity display names in the middle, and the current activity name as the rightmost crumb. Each crumb is a clickable link that pops the stack back to that depth. The back affordance pops one level. Clicking the source-surface label dismisses Activity Detail entirely and returns the player to the opener surface with its context intact — the dossier tab still selected, the queue row still highlighted, the objectives row still expanded.
Display labels for Routine and Composite. When a run belongs to a recognizable composed approach — a sequence of activities executed together for a mission or a recurring routine — Activity Detail may show a planning-layer readback label such as "part of: Overnight Camp Routine" or "step 2 of 4 in: Water Lead Investigation". These labels are derived from the planning context the opener passed in (from Current Plan, Objectives, or Mission state). They are purely readback labels; they do not commit to a runtime type, a source keyword, or a hierarchy that is not already present in the read model.
Panel Mode Vocabulary
The shell supports four panel modes. Every screen and dossier declares which modes it supports. The vocabulary is shared with gd-combat-dungeon-screen.md and gd-territory-dossier.md.
- Compact — the default. Keeps the map visibly dominant. Answers "what is this / what matters now?" Compact reads the current selection and surfaces a small, dense panel of headline state, top quick activities, and a cue to open a deeper surface.
- Wide — provides deep inspection while preserving shell context. Answers "what can I do / what are the consequences?" Wide adds activity preview, tabbed inspection (Overview / Activities / Features / Approaches / Threats / Sites for Territories), queue and schedule buttons, activity row hover tooltips and blocked-reason chips (client-derived today; server-confirmed blocked reason is Wave B-owned, pending sync), and known versus unknown facts. Activity row clicks open the shared Activity Detail inspector with dossier opener context preserved (see § Activity Detail).
- Full — a distinct planning board. Answers "how do I plan, compare,
manage, optimize?" Full is not a stretched copy of the wide layout; it is
a different component (
TerritoryPlanningBoardfor Territories) with a multi-row layout combining spatial overview, route preview, activity planning, neighbour comparison, camp-suitability comparison, and queue preview. Reusing wide-mode tabs inside the full board is allowed; describing full mode as "wide with more horizontal space" is wrong. - Fullscreen — the surface owns the entire client work area, hiding the
map under it. Reserved for opt-in destinations the player chose to look at,
such as the combat tab's three-view List / Summary / Watch flow. See
gd-combat-dungeon-screen.md§3 for the canonical fullscreen contract.
The Territory Full panel and the Combat Fullscreen panel are distinct modes; they share the "the map is no longer the primary visible surface" property but differ in component composition, dismissal model, and shell-rail visibility.
ContextPanel
TerritoryContextPanel is the canonical right-click ContextPanel for the
strategic map. It is the only Territory quick-activity entry point that lives
outside the compact / wide / full panel modes.
ContextPanel contract:
- Anchored at the pointer position on right-click, viewport-clamped so the panel never escapes the visible map area.
- Non-blocking. The underlying map remains interactive; the player can scroll or pan and the ContextPanel dismisses.
- Dismiss on outside click, Escape, activity selection, or Territory selection change.
- Groups activities into four category labels: Travel, Survival,
Explore, Planning. The category labels are presentation grouping, not
a new behavior taxonomy; every entry routes through the canonical activity
surface (
enqueueScheduledActivity,enqueueTravel, oruseOverlayStore.open) and there is no parallel command path. - Dangerous or branching activities (e.g. found-camp, enter ruin, dangerous travel route) surface a FloatingSurface modal from the ContextPanel selection rather than performing the activity inline.
- Non-dangerous activity entries may be opened in the shared Activity Detail
inspector before enqueue. The "tap activity name" affordance opens Activity
Detail with
source: 'context-panel'and the territory id preserved; the inline Queue affordance on the same row still enqueues directly. The inspect-before-queue path is the Wave M target architecture — ContextPanel currently routes selections directly to enqueue (see § Activity Detail — Implementation status).
The category names Travel / Survival / Explore / Planning are category labels in the menu UI. They are not a new SECS planning noun, they are not a new behavior category, and they do not introduce new content vocabulary.
Modal Taxonomy
FloatingSurface modals are reserved for moments that demand player attention.
The shell uses two new modals on top of the existing PendingChoiceModal and
CombatDetailModal baseline:
ActionConfirmModal— two-button Confirm / Cancel, driven byuxModalStore. Used for dangerous activity confirmations such as found-camp / establish-camp, enter ruin, dangerous travel route.FeatureDiscoveryModal— choices array becomes buttons; otherwise an Acknowledge button. Used for major discovery, ritual or nexus interaction, feature reveal, and similar narrative beats.
The full modal allowlist for the Valenar shell:
| Use modals for | Do NOT use modals for |
|---|---|
Story or branching choice (PendingChoiceModal, FeatureDiscoveryModal) | Forage |
| Major discovery | Gather branches |
Dangerous activity confirmation (ActionConfirmModal) | Normal scouting |
| Found camp / establish camp | Simple queue activity |
| Enter ruin | Basic tooltip information |
| Ritual or nexus interaction | Repeated routine activities |
Combat result (CombatDetailModal until retired; see combat-dungeon doc) | Read-only inspection |
| Event resolution | Hovering a marker |
| Route customization (planned, beyond Wave C) | |
| Major activity outcome |
Modal abuse is a regression signal. If a routine activity grows a confirmation modal, the design has drifted; resolve by demoting to ContextPanel selection or a queue blocked-reason chip.
Overlay Layering
The shell composes multiple overlays over the strategic map. Layer order matters; later layers occlude earlier ones.
- Map canvas (Territories, terrain, polygons).
- Travel paths (existing queue / planned travel lines).
- Markers — far / mid / close zoom passes per ../systems/gd-map-markers.md.
- Route preview overlay (pre-enqueue route line, destination marker, danger
per segment, nightfall risk, known and unknown segment shading). The route
preview is local-state, not part of
overlayStore. - Characters and animated entities.
- Tooltip layer (hover-anchored, read-only).
- ContextPanel layer (anchored, non-blocking).
- FloatingSurface modal layer (centered or anchored, blocking).
Fullscreen panels (e.g. the combat tab) replace layers 1–4 with their own surface and remove tooltip and ContextPanel routing scoped to the map until the fullscreen panel dismisses.
Design Rules
- New screens declare whether they support compact, wide, full, or fullscreen modes. A screen that needs more depth than wide must specify whether it belongs in full (planning board, same shell context) or fullscreen (dedicated surface).
- Compact / wide / full / fullscreen vocabulary is shared across Core, Camp, Character, Objectives, Queue, Settlement, Territory, and Combat surfaces. Cross-doc drift is a process FAIL.
- Tooltip, ContextPanel, and FloatingSurface are three distinct abstractions. Do not extend the tooltip engine with interactive controls; do not turn the ContextPanel into a blocking surface; do not promote the FloatingSurface modal into a routine-activity dispatch path.
- Deep information opens because the player selected a place, actor, or system the map already made relevant. Right-click quick activities surface through the ContextPanel, not through map context menus that bypass the activity queue.
- Map markers are presentation, not entities. The shell never introduces a new server-side entity type to back a marker; see ../systems/gd-map-markers.md.
- Right-click is part of the canonical interaction contract on the strategic map. CK3 inspirations apply only to map-first shell behavior, marker readability, route danger overlay, and focused activity modals.
Implementation status
| Surface / Behavior | Shipped wave | Status |
|---|---|---|
| Activity Detail — description, costs, rewards, queue button | Wave C | Ships via the Activities panel expansion in the mc/activities surface |
| Activity Detail — Queue-origin opener from queue navigator | Wave C | Ships via the queue navigator row click + Activities panel; opener-context tag 'queue' is the Wave M target |
| Activity Detail — Territory Dossier Activities tab row click | Wave C | Partial: row click navigates to mc/activities panel; dossier back-stack and 'dossier' opener-context tag are deferred (Wave C / Wave D follow-through) |
| Activity Detail — Activity Catalog opener (Activities panel) | Wave C | Ships: here / reachable / all filters plus the activity panel expansion; opener-context tag 'catalog' is the Wave M target |
| Activity Detail — ContextPanel "tap activity name" inspect path | Deferred | ContextPanel currently routes selections directly to enqueue without an Activity Detail step; the inspect-before-queue path is the Wave M target architecture and requires Wave C client work |
| Activity Detail — Current Plan and Objectives activity link openers | Deferred | Current Plan is not yet a first-class screen; objective-to-activity link chips remain deferred |
| Activity Detail — Recursive drill-stack via run parent / child references | Deferred, Wave B | Requires the activity run snapshot to project the engine's parent-run / child-run references — Wave B-owned, pending sync |
| Activity Detail — Journal activity link opener | Deferred | Journal-to-activity navigation remains deferred |
| Server-confirmed blocked-reason chip on queue rows | Deferred, Wave B | Currently client-derived; the server availability snapshot carries a blocked-reason field, the queue snapshot does not — Wave B-owned, pending sync |
| ScoutApproach wire-id rename and legacy lead-activity retirement | Wave B | Wave B-owned, pending sync; Wave M doc examples use the canonical name ScoutApproach |
Cross-Links
- gd-current-plan.md
- gd-territory-dossier.md
- gd-objectives-screen.md
- gd-queue-screen.md
- gd-camp-screen.md
- gd-journal.md
- gd-combat-dungeon-screen.md
- ../catalogs/gd-activity-catalog.md
- ../systems/gd-map-markers.md
- ../systems/gd-objectives-clues-missions.md
- ../../../../docs/adr/ad-0002-ui-surface-taxonomy.md
- ../../../../docs/adr/ad-0003-map-marker-presentation-layer.md