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
updateProjectSettingsexport; retiresetPreviewImage - 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: returnsundefinedwhenprojectIddoesn't resolve, caller throws the 404. descriptionomitted/blank clears the stored value (stored asundefined), matching howcreateProjectalready treats an empty description.previewImageis only overwritten when passed — omitting it leaves the project's currentpreviewImageuntouched, so Save works with just a name/description change and no new file.- No change to
MapProject's shape ordata/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 forpreviewImageNameinproject.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.shplaceholder and theAttachment/Field/FieldLabel/Input/Textareaprimitives already used inhome.tsxandcreate.tsxmeans 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.tsexportsupdateProjectSettings(projectId, updates)with the signature above, followingaddMap's not-found (undefined) convention.- Blank/omitted
descriptionclears the stored description. previewImageis only overwritten when provided inupdates.setPreviewImageis left in place, unchanged, for this task — it's still called by the currentproject.tsxaction and will be retired in T2.data/projects.json's existing entries remain valid with no migration.pnpm typecheckpasses.
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 optionalpreviewImagefile fromrequest.formData(). - A missing/empty
namereturns{ error: "Name is required." }(same message/shape ascreate.tsx) without touching stored data. - When a
previewImagefile is present, it's validated and saved via the existingsaveUpload(); 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.projectIdstill throwsnew Response("Not Found", { status: 404 }). setPreviewImageis no longer imported/called fromproject.tsx; once confirmed unused elsewhere, its export is removed fromprojects.server.ts.pnpm typecheckpasses.
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
isEditingboolean (useState, defaultfalse): shows the project name, description (or "No description"), and the preview image — or thehttps://avatar.vercel.sh/{project.name}placeholder whenpreviewImageis unset — plus a single "Edit" button. No form controls render in this state. - Clicking "Edit" sets
isEditingtotrue, revealing a<Form method="post" encType="multipart/form-data">containing:Field/FieldLabel/Inputfor name (pre-filled withproject.name,required),Field/FieldLabel/Textareafor description (pre-filled withproject.description), and the existingAttachment-based file picker forpreviewImage(optional, same accepted types as today), plus "Save" and "Cancel" buttons. - Clicking "Cancel" sets
isEditingback tofalsewithout submitting the form or mutating any loaded data. FieldErrorrendersactionData?.errorinside the edit form; after a failed submit,isEditingstaystrueso 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 typecheckpasses.- 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.