Skip to content

Map list preview & details

Goal

In the project detail view, move "Add map" from the page header into the map list section, and show a small preview and details for each map — mirroring how the home page's project list already shows a thumbnail and description.

Decisions

Decision Choice Why
Plan file location docs/v0.0.2/map-list-preview.md Matches the existing versioned precedent in this folder (project-preview-image.md, map-upload.md)
Thumbnail source Reuse the map's own stored path directly for kind: "upload" maps; a placeholder icon for kind: "tileSet" maps Confirmed with user — every map creatable via the UI today is kind: "upload" and already has a stored image path; generating real tile-set thumbnails is a bigger, separate effort
Map details Add an optional description field to MapRef, captured on the existing add-map form; fall back to a source-derived label (e.g. "Uploaded image" or the tile set's label) when unset Confirmed with user — MapRef has no description today, unlike MapProject
Editing description later Not in this task No edit flow exists for any map field yet; deferred to the "Map View: Mode Switching" backlog item, same as project settings
"Add map" placement Right-aligned next to the "Maps" heading, always visible (including the empty state) Confirmed with user
Parent map select in add-map.tsx Left untouched Already inert today (not read by the action) — wiring it up is the separate "submaps" backlog idea, out of scope here

Scope

In:

  • MapRef gains an optional description
  • add-map.tsx gains an optional description field, saved on create
  • project.tsx: "Add map" button moves next to the "Maps" heading; each map row gets a thumbnail (ItemMedia) and details (ItemTitle + ItemDescription)

Out:

  • Generating real thumbnails for tileSet-kind maps from their tile pyramid
  • Any edit flow for an existing map's name/description
  • Wiring up the "Parent Map" select
  • Any change to the project-level settings/preview-image flow

Data model

// app/lib/projects.server.ts
export type MapRef = {
  id: string
  name: string
  description?: string
  source: MapSource
  createdAt: string
}

data/projects.json, data/demo.json, data/demo2.json need no change — the field is optional and existing maps just render the fallback label, unchanged from today.

Why this shape

  • A bare optional description?: string on MapRef matches the shape MapProject.description already uses — no new convention.
  • Deriving a fallback label from source (rather than requiring a description) means every existing map — none of which have one — still renders something meaningful instead of blank text.
  • Keeping the thumbnail logic to "upload path or placeholder" avoids building tile-set thumbnail generation, which no seed data or UI flow needs yet (kind: "tileSet" isn't reachable from any route today).

Tasks

Ordered so each task type-checks and is shippable on its own; later tasks depend on earlier ones.

T1 — Data model: description on MapRef

Goal: Represent an optional description on a map.

Acceptance criteria:

  • app/lib/projects.server.ts's MapRef gains description?: string as shown above.
  • addMap(projectId, { name, description, source }) accepts an optional description and stores it on the created map when present.
  • data/projects.json's existing maps remain valid with no description present.
  • pnpm typecheck passes.

T2 — Add-map flow: capture a description

Goal: A user can optionally describe a map while creating it.

Acceptance criteria:

  • add-map.tsx's form gains an optional Textarea field (name="description"), using the existing Field/FieldLabel pattern already in that form.
  • The field is optional — submitting without one still creates the map exactly as today.
  • The action reads description from request.formData(), trims it, and passes it to addMap() only when non-empty (otherwise undefined).
  • pnpm typecheck passes.

T3 — Fallback label for maps without a description

Goal: A map with no description still shows a meaningful detail line.

Acceptance criteria:

  • A small pure function (e.g. mapSourceLabel(source: MapSource): string, defined in project.tsx alongside the route since it's display-only) returns "Uploaded image" for kind: "upload" and the resolved tile set's label (via getTileSet from tile-sets.ts) for kind: "tileSet", falling back to "Tile set" if the id doesn't resolve.
  • pnpm typecheck passes.

T4 — Project page: move "Add map", render preview + details

Goal: The Maps section owns its own "Add map" action, and each row shows a thumbnail and details, matching the home list's pattern.

Acceptance criteria:

  • The CardAction/"Add map" Button is removed from CardHeader in project.tsx.
  • The "Maps" heading and an "Add map" button (render={<Link to=".../map/new" />}) sit in one right-aligned row, visible whether or not project.maps is empty (replacing the current empty-state-only text branch).
  • Each map Item gains an ItemMedia variant="image" containing an <img> with src={map.source.path} when map.source.kind === "upload", or an ItemMedia with a placeholder ImageIcon otherwise.
  • Each map Item's ItemContent gains an ItemTitle (map.name, as today) and an ItemDescription showing map.description ?? mapSourceLabel(map.source).
  • Manual check: pnpm dev, visit a project with an uploaded map — its row shows the actual uploaded image and either its description or "Uploaded image". Temporarily adding a kind: "tileSet" entry to data/demo.json confirms the placeholder-icon and tile-set-label branch, then revert it.
  • pnpm typecheck passes.