Skip to content

Project detail view: Settings

Goal

Replace the always-visible preview-image upload form on the project detail page with a read-only-by-default Settings section covering name, description, and preview image, that switches to an editable form via an explicit "Edit" action.

Decisions

Decision Choice Why
Plan file location docs/v0.0.2/project-settings.md Follows the existing per-feature file precedent in this folder (e.g. project-preview-image.md) rather than folding into the versioned release doc
Scope Name, description, and preview image all editable in this task Confirmed with user — the data model already has all three fields on MapProject, only the update path for name/description was missing
Server write path One new export, updateProjectSettings, replaces setPreviewImage A single form submits all three fields together; two write paths for one POST would be redundant. setPreviewImage becomes dead code once the action stops calling it
Edit toggle Client-side useState (isEditing), no route/URL change Confirmed with user — matches the existing previewImageName local-state pattern already in project.tsx
Cancel Explicit "Cancel" button, resets to read-only without submitting Confirmed with user
Empty state Same https://avatar.vercel.sh/{project.name} placeholder used on home.tsx Confirmed with user — visual consistency across the app
Section placement Below the Maps list Confirmed with user
Preview image requirement Changes from required to optional on submit The current action rejects a submission with no file; once Save also covers name/description, a file must not be mandatory

Scope

In:

  • app/lib/projects.server.ts — new updateProjectSettings export; retire setPreviewImage
  • app/routes/project.tsx — action rewritten to accept name/description/optional-image; component restructured into a read-only/edit Settings section below the Maps list

Out:

  • Moving the "Add map" button out of the header (separate backlog line)
  • Any settings beyond name, description, preview image
  • Deleting orphaned preview-image files when replaced
  • Changes to home.tsx or create.tsx beyond visually confirming they still match

Data model

// app/lib/projects.server.ts
export function updateProjectSettings(
  projectId: string,
  updates: { name?: string; description?: string; previewImage?: string }
): MapProject | undefined
  • Follows addMap's existing not-found convention: returns undefined when projectId doesn't resolve, caller throws the 404.
  • description omitted/blank clears the stored value (stored as undefined), matching how createProject already treats an empty description.
  • previewImage is only overwritten when passed — omitting it leaves the project's current previewImage untouched, so Save works with just a name/description change and no new file.
  • No change to MapProject's shape or data/projects.json.

Why this shape

  • One combined setter (rather than separate setName/setDescription/ setPreviewImage) mirrors the single form that submits all three fields together — there's no scenario in this UI where they're saved independently.
  • Keeping the edit state as local useState (as already done for previewImageName in project.tsx) needs no new routing and matches the house pattern for this route; a URL-driven edit mode would be new infrastructure for a single-section toggle.
  • Reusing the avatar.vercel.sh placeholder and the Attachment/ Field/FieldLabel/Input/Textarea primitives already used in home.tsx and create.tsx means no new UI components are needed.

Tasks

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

T1 — Data model: updateProjectSettings

Goal: Add a way to persist name, description, and optionally a new preview image on an existing project in one write.

Acceptance criteria:

  • app/lib/projects.server.ts exports updateProjectSettings(projectId, updates) with the signature above, following addMap's not-found (undefined) convention.
  • Blank/omitted description clears the stored description.
  • previewImage is only overwritten when provided in updates.
  • setPreviewImage is left in place, unchanged, for this task — it's still called by the current project.tsx action and will be retired in T2.
  • data/projects.json's existing entries remain valid with no migration.
  • pnpm typecheck passes.

T2 — Route action: name/description, optional image

Goal: project.tsx's action updates name, description, and (optionally) the preview image in a single POST, and stops requiring a file.

Acceptance criteria:

  • The action reads name, description, and an optional previewImage file from request.formData().
  • A missing/empty name returns { error: "Name is required." } (same message/shape as create.tsx) without touching stored data.
  • When a previewImage file is present, it's validated and saved via the existing saveUpload(); a failure returns { error: uploaded.error } without touching stored data or requiring a file to be present at all.
  • On success, calls updateProjectSettings(params.projectId, { name, description: description || undefined, previewImage: <uploaded path if any> }) and redirects to /project/:projectId.
  • A non-resolving params.projectId still throws new Response("Not Found", { status: 404 }).
  • setPreviewImage is no longer imported/called from project.tsx; once confirmed unused elsewhere, its export is removed from projects.server.ts.
  • pnpm typecheck passes.

T3 — Route UI: read-only Settings section with Edit/Cancel

Goal: The project page shows a read-only Settings section by default, below the Maps list, switching to an editable form via a client-side "Edit" toggle.

Acceptance criteria:

  • A Settings section renders below the Maps list (Maps list keeps its current position).
  • Default (read-only) state, driven by a local isEditing boolean (useState, default false): shows the project name, description (or "No description"), and the preview image — or the https://avatar.vercel.sh/{project.name} placeholder when previewImage is unset — plus a single "Edit" button. No form controls render in this state.
  • Clicking "Edit" sets isEditing to true, revealing a <Form method="post" encType="multipart/form-data"> containing: Field/FieldLabel/Input for name (pre-filled with project.name, required), Field/FieldLabel/Textarea for description (pre-filled with project.description), and the existing Attachment-based file picker for previewImage (optional, same accepted types as today), plus "Save" and "Cancel" buttons.
  • Clicking "Cancel" sets isEditing back to false without submitting the form or mutating any loaded data.
  • FieldError renders actionData?.error inside the edit form; after a failed submit, isEditing stays true so the open form isn't lost.
  • The previous always-visible, image-only upload form (formerly rendered directly in CardContent) is removed and fully replaced by this section.
  • pnpm typecheck passes.
  • Manual check: pnpm dev, open a project, confirm the read-only view shows current values, "Edit" reveals the form, "Cancel" discards in-progress changes, and "Save" persists name/description/image and returns to the read-only view.