Refine Edit Sidebar Object Creation¶
Decisions¶
| Decision | Choice | Why |
|---|---|---|
| Sidebar navigation | Replace the three direct Point/Line/Area creation buttons with Points, Lines, and Areas tabs | Geometry is now a category of available object types rather than the creation action itself |
| Creation trigger | Only an object-type button starts placement or drawing | Prevents a geometry-only action from creating an object without a meaningful configured type |
| Type source | Use the current project's poiTypes, lineTypes, and areaTypes supplied by the map route loader |
These definitions already contain the display name, slug, and optional parent group needed by the sidebar |
| Grouping | Render grouped types beneath their group heading and ungrouped types beneath an explicit Ungrouped heading |
Gives categorized and uncategorized definitions a consistent, scannable layout |
| Empty kinds | Keep the tab visible and show an empty state when its type list is empty | Preserves stable Points/Lines/Areas navigation without offering an invalid generic action |
| Placement state | Store the selected type independently for POIs, lines, and areas and pass it into the existing add forms | The selected button must determine the initial type while the creation form remains editable |
| Tab persistence | Persist the active creation tab per map using a dedicated map-scoped localStorage key | Matches the existing map-specific visibility-tab behavior without conflating creation and visibility state |
| Server contract | Reuse the existing add intents and type validation in project-map.tsx |
The server already validates project-defined types and persists the resulting map object |
| Directus schema | No schema change and no new Directus field | The feature reads existing project payload data only; no directus/schema/*.json snapshot exists in this workspace |
Prerequisites¶
- Keep the protected route declaration unchanged:
project/:projectId/map/:mapIdcontinues to renderapp/routes/project-map.tsxthroughapp/routes.ts. - Preserve the existing
Route.LoaderArgsandRoute.ComponentPropscontracts. The loader already returnsprojectwithpoiTypes,lineTypes, andareaTypes, plus the current map objects. - Keep filesystem and Directus access inside
app/lib/*.server.ts; this feature does not need a new loader, action, persistence function, or server module. - Preserve the existing add action intents:
addfor POIs,add-linefor lines, andadd-areafor areas. The selected type is a client-side initial value submitted through the existing forms. - Keep Leaflet client-only behavior unchanged. The sidebar and editing-state changes must not import Leaflet or move map drawing logic across the client boundary.
- Use the existing
Tabs,TabsList,TabsTrigger,TabsContent,Button, and layout primitives. ReusegroupByFieldfrom~/lib/utilsfor deterministic grouped rendering. - Confirmed schema status: no
directus/schema/*.jsonfiles are present, and this task intentionally reads and writes no Directus field.
Step 1 - Extend editing state with selected creation types¶
Goal: Give each creation mode a selected object type that can be initialized by a sidebar button and consumed by the existing creation forms.
Scope
- In:
app/features/map-editor/use-map-editing-state.ts, add selected type state for POIs, lines, and areas using the existingPoiType,LineType, andAreaTypealiases. - In: the same hook, add type-aware start handlers that select the requested type before activating POI placement, line drawing, or area drawing; retain the current mutual-exclusion and draft-clearing behavior when switching geometry modes.
- In: the same hook, preserve selected type values while a creation form is open and clear or reinitialize them only when the corresponding creation flow requires it; do not alter object selection/editing semantics.
- In:
app/routes/project-map.tsx, expose the selected types and type-aware handlers from the hook to the route composition. - Out: Server validation changes, changes to persisted map-object shapes, or replacing the existing form type controls.
Acceptance criteria
- POI, line, and area creation each have an explicit selected type value available to the route.
- Starting one geometry mode still cancels the other active placement/drawing modes and clears their drafts as before.
- Starting placement/drawing through a type-aware handler records the clicked type before the creation form opens.
- Existing object selection, edit-mode transitions, and mode cleanup retain their current behavior.
-
pnpm verify typecheckpasses.
Step 2 - Pass selected types through the creation forms¶
Goal: Use the type chosen in the sidebar as the initial value for the existing POI, line, and area creation forms without removing later type editing.
Scope
- In:
app/routes/project-map.tsx, pass the selected POI, line, or area type into the relevantMapObjectFormdefault values when rendering a new-object form. - In:
app/routes/project-map.tsx, ensure the add form submission still includes the selected type under the existingtypefield and continues usingadd,add-line, oradd-areaas appropriate. - In:
app/features/map-objects/map-object-form.tsx, preserve the current editable type control and make its initialization robust when a selected type is present, including grouped and ungrouped definitions. - In: the same form, retain the existing fallback behavior for an unknown type when editing legacy data; do not lock the selected type after placement.
- Out: Changes to action validation, project type management, or map-object persistence.
Acceptance criteria
- A point type button opens point placement and the new POI form initially selects that type.
- A line type button opens line drawing and the new line form initially selects that type.
- An area type button opens area drawing and the new area form initially selects that type.
- Users can change the type in the creation form before saving.
- Existing edit forms retain their current type and grouping behavior.
-
pnpm verify typecheckpasses.
Step 3 - Replace sidebar creation controls with grouped type tabs¶
Goal: Make the edit sidebar expose configured object types as the only creation actions.
Scope
- In:
app/features/map-editor/edit-sidebar.tsx, replace the current three creation buttons with aTabsinterface for Points, Lines, and Areas. - In: the same component, accept the three project type-definition arrays and type-aware creation handlers as props; remove the geometry-only toggle props once no longer used.
- In: the same component, render one button per available definition using its display
name, and group definitions withgroupByFieldso each category has a heading. Render anUngroupedheading for definitions without a group. - In: the same component, show a concise empty state inside a tab with no configured definitions and no generic creation button.
- In: the same component, preserve the existing active-placement affordance so a selected creation action communicates that the map is waiting for input, while keeping the sidebar width and surrounding Layers/Height Map/Nav Mesh sections stable.
- Out: Changes to layer controls, map drawing primitives, object detail panels, or global styling.
Acceptance criteria
- The three old direct Point/Line/Area creation buttons are replaced by Points, Lines, and Areas tabs.
- Switching tabs does not start placement or drawing.
- Each configured type appears exactly once as a creation button in its matching tab.
- Categorized buttons are displayed under their parent category heading.
- Ungrouped buttons are displayed under an
Ungroupedheading. - Empty type lists show an empty state and no invalid fallback action.
- Clicking a type button starts the matching type-aware placement or drawing flow.
- The remaining sidebar sections retain their current order and behavior.
-
pnpm verify typecheckpasses.
Step 4 - Persist and hydrate the active creation tab per map¶
Goal: Restore the user's last Points/Lines/Areas creation tab for each map without persisting transient placement state.
Scope
- In:
app/features/map-editor/edit-sidebar.tsxor a small editor-local hook, define a map-scoped localStorage key and validated tab union forpoi,line, andarea. - In:
app/routes/project-map.tsx, pass the stablemap.idinto the sidebar and keep tab hydration client-only through the existing localStorage helpers. - In: the chosen editor-local state boundary, guard writes until the current map's stored tab has been read, reset to Points when a stored value is missing or invalid, and isolate state when
map.idchanges. - In: preserve active placement/drawing state as transient; changing or rehydrating the tab must not resurrect drafts or start an action.
- Out: Reusing the visibility-control storage key, persisting selected object types, persisting open forms, or changing the broader map-mode persistence contract.
Acceptance criteria
- Returning to a map restores its last valid creation tab.
- Two maps do not share their active creation tab.
- Invalid, missing, or unavailable localStorage values fall back to Points without a user-facing error.
- Initial defaults are not written before hydration completes.
- Switching tabs never starts placement/drawing or changes selected object state.
-
pnpm verify typecheckpasses.
Step 5 - Tune responsive and active-state presentation¶
Goal: Keep grouped type buttons readable and usable in the narrow and wide edit-sidebar layouts.
Scope
- In:
app/features/map-editor/edit-sidebar.tsx, tune local spacing, button sizing, wrapping, category headings, tab sizing, and empty-state layout for the existingw-36 md:w-64sidebar. - In: use the existing button variants and type definition metadata; do not introduce a new icon registry or require icons for line/area types that do not define them.
- In: ensure the active placement/drawing state remains visible without causing sidebar width changes or overlapping the category/button content.
- Out: Global CSS changes, redesigning the sidebar, or changes to unrelated map controls.
Acceptance criteria
- Type names fit or wrap within the sidebar without horizontal overflow.
- Category headings remain visually distinct from their buttons.
- Tabs and buttons remain usable at narrow widths.
- Active placement/drawing state is understandable and does not overlap neighboring controls.
- The Layers, Height Map, and Nav Mesh sections remain visually stable.
- Manual responsive checks show no relevant console errors or layout warnings.
Step 6 - Verify the complete creation flow¶
Goal: Confirm the new sidebar navigation, type selection, grouped rendering, persistence, and existing save behavior work together.
Acceptance criteria
-
pnpm verify typecheckpasses. -
pnpm verify formatcompletes successfully. -
pnpm verify buildpasses. - Manual check: enter edit mode on a map with point, line, and area types and confirm the sidebar opens with the persisted or default Points tab.
- Manual check: switch tabs and confirm no placement or drawing begins until a concrete type button is clicked.
- Manual check: click a categorized point type, place it, confirm the creation form is preselected to that type, change the type, and save successfully.
- Manual check: repeat the preselection, editable-type, and save flow for a line and an area.
- Manual check: confirm grouped definitions appear under their parent headings and ungrouped definitions under
Ungrouped. - Manual check: confirm a kind with no configured types shows only its empty state.
- Manual check: switch to another map and confirm its creation tab is independent; reload and confirm valid tab persistence.
- Manual check: cancel or switch away from an active creation flow and confirm drafts, placement state, and selections follow the existing cleanup rules.
- Confirm no changes were made to
app/routes.ts, server persistence modules,data/projects.json, or Directus schema/collections.
Risks / open questions¶
- The existing editor hook exposes geometry-only toggle handlers and route-level form rendering is spread across multiple conditional branches. The implementation should update the smallest shared state boundary first, then adapt each creation branch so selected types cannot be lost between placement and form display.
- The default type definitions currently include line and area types without icons. The plan intentionally uses names and existing button styling rather than assuming every type has an icon field.
- There is no
directus/schema/*.jsonsnapshot in the repository. This is not blocking because the plan reads existingMapProjecttype arrays and adds no Directus field or server persistence. - The existing v0.0.8 local-storage plan includes active-tab persistence for visibility controls, but creation-tab state must use a separate key to avoid cross-feature coupling.