Refactor the map renderer from a large vanilla JavaScript application into a Vue application while preserving its two deployment modes:
- Static mode for GitHub Pages and other read-only deployments
- Dynamic local mode for catalog editing and local data updates
The refactor should reduce the size and complexity of the current `src/public/app.js` entrypoint without breaking the existing viewer behavior, static export workflow, or catalog editing workflow.
## Current Baseline
The renderer already has a useful split between source files and deployment artifacts:
-`src/public/app.js` currently orchestrates most of the browser app.
-`src/public/map-catalog-ui.js`, `src/lib/catalog.js`, and related modules already isolate some catalog logic.
-`site/` contains the committed static bundle used for GitHub Pages.
-`package.json` already exposes separate static and dynamic scripts.
- The root README already documents static versus dynamic usage.
That means the migration should be an incremental extraction, not a from-scratch rewrite.
The earlier Phase 2 checkpoint is now outdated. The renderer has moved further in both the rendering pipeline and the runtime split, but the Vue migration is still only partial.
### What is true now
- The viewer is already atlas-and-scene driven rather than a single baked raster surface.
- Static export, normal dynamic viewing, and explicit admin catalog-editing modes already exist.
- The Vue shell is no longer just one root component; it now owns the side panel, viewport shell, tooltip mount, and egg editor modal markup.
- The active browser runtime now enters through the Vue shell and Vue controller modules under `map_renderer/src/vue`.
- The old public browser coordinator files have been removed from `map_renderer/src/public`.
- Catalog editing works today, but until this update it still rendered through legacy `innerHTML` tooltip markup instead of a Vue-managed editor surface.
### Main architectural gap before this update
- Runtime mode selection existed functionally, but static versus dynamic behavior was still chosen through scattered helper checks and hard-coded URL branches across the legacy modules.
- The pinned tooltip and catalog editor were still procedural DOM output, which meant Phase 4 behavior existed without actually being migrated into Vue.
### Main architectural gap after this update
- Vue now owns the tooltip/editor surface, but the legacy app still computes most tooltip content and still owns the underlying editor actions.
- Runtime mode and data-path selection are now centralized, but higher-level shared state is still concentrated in the legacy `app.js` entrypoint rather than in composables or services.
- The remaining heavy work is no longer removing the old public bootstrap; that is done.
- The remaining heavy work is deeper cleanup: extracting the large Vue controller into smaller composables/services and replacing the remaining imperative DOM coordination inside that controller with more idiomatic Vue state flow.
1. Keep a static deployable build that can run from committed artifacts with no file-system write capability.
2. Keep a dynamic local build that can edit the Catalog and write changes back to disk.
3. Preserve the existing viewer workflow during migration so the app remains usable at every step.
4. Keep catalog editing opt-in and unavailable in static deployments.
5. Avoid coupling the UI framework change to data format changes unless a boundary must move for architectural reasons.
## Target Architecture
The Vue refactor should split the app into four layers:
1. App shell and routing-like mode selection
- Owns top-level mode detection: static versus dynamic.
- Chooses the correct feature set, status messages, and edit affordances.
- Remains thin and delegates real work to feature components and composables.
2. View components
- Replace the current procedural DOM wiring with Vue components for the map controls, viewport, catalog panel, tooltips, modal editor, and export actions.
- Keep components small and purpose-specific.
- Prefer composition over a single mega component.
3. Composables and state services
- Move map selection, build triggers, UI flags, and interaction state into composables or store-like modules.
- Keep business rules out of templates.
- Preserve existing helper modules where they already work.
4. Data access adapters
- Introduce a strict boundary between read-only data access and writable catalog access.
- Static mode uses read-only adapters only.
- Dynamic mode uses writable adapters for catalog updates and local server APIs.
## Mode Model
The app should continue to behave as one codebase with two runtime modes.
### Static Mode
- Serves prebuilt assets from `site/` or equivalent deployed output.
- Allows viewing, filtering, inspecting, and downloading data.
- Does not expose any actions that would write to disk or call write-enabled server endpoints.
- Can still show the Catalog UI if the data is available, but all editing controls must be removed or disabled.
### Dynamic Mode
- Runs against the local Node server.
- Can edit Catalog data through the UI.
- Can write CSV updates to disk.
- Can refresh or rebuild derived data as needed after edits.
- Must preserve the same viewer experience as static mode wherever the data model overlaps.
## Proposed Migration Phases
### Phase 0: Freeze the boundary
Goal: identify the stable interfaces before moving UI code.
- Inventory the current `app.js` responsibilities.
- Group code into categories: rendering, state mutation, map loading, catalog updates, overlays, modals, downloads, and notifications.
- Identify the minimum data contracts between the viewer, catalog, and scene APIs.
- Decide which existing helper modules stay unchanged and which become composables or services.
Deliverable:
- A written boundary map for the current app and a component breakdown for Vue.
### Phase 1: Scaffold Vue without changing behavior
Goal: get Vue running while preserving the current app.
- Create a Vue 3 application shell.
- Move the current page layout into Vue root components.
- Keep the existing data modules and API helpers in place.
- Mount the viewer in Vue but continue using the current scene and catalog logic.
- Add a compatibility layer so existing DOM-driven utilities can be replaced one section at a time.
Deliverable:
- The app loads through Vue, but functionality remains equivalent to the current version.
### Phase 2: Split the UI into feature components
Goal: remove the largest chunks of imperative DOM code.
- Break the viewer into components such as header/control bar, map selector, viewport, catalog panel, export area, status area, and editor modal.
- Move local UI state into Vue reactive state or composables.
- Replace direct DOM updates with props, emits, and computed state.
- Keep the map rendering and scene logic isolated so the UI migration does not change map semantics.
Deliverable:
- The main app flow no longer depends on a monolithic procedural entrypoint.
### Phase 3: Formalize static and dynamic adapters
Goal: separate read-only rendering from writable editing behavior.
- Introduce a read-only adapter for static deploys.
- Introduce a writable adapter for the local Catalog editor.
- Make the app choose adapters based on mode, not scattered conditionals.
- Completed earlier: the runtime already supported both static exported scenes and dynamic live-build scenes from one codebase.
- Completed in this batch: the client now has a shared runtime adapter layer that centralizes mode detection, capability checks, and data-path selection for catalog data, scene data, atlases, and NPC spawner metadata.
- Completed in this batch: the legacy browser modules no longer hard-code most static-versus-dynamic URL branching inline; they consume the shared adapter instead.
- Completed in this batch: the active runtime controller moved under `src/vue/controller`, so the browser no longer boots through the old `src/public/app.js` path.
- Result: the mode boundary is now explicit in code and reusable from both Vue-facing and legacy-facing modules.
- Remaining: move more page-level behavior from the Vue controller into smaller Vue-aware composables so the app shell owns mode/config decisions through smaller units rather than one large controller module.
- Completed earlier: catalog editing stayed admin-only and the write path already updated CSV-backed catalog data.
- Completed in this batch: the pinned tooltip and catalog editor form now render through the Vue tooltip component instead of legacy `innerHTML` generation.
- Completed in this batch: save, hide, warp-copy, egg-edit launch, and monster-spawner action hooks now cross the Vue boundary through an explicit tooltip bridge instead of ad hoc DOM listeners on generated markup.
- Completed in this batch: static export now uses the Vue production shell as its site base instead of the removed public browser shell.
- Result: the highest-value editable UI path is now under Vue control while preserving the existing catalog save and rebuild behavior.
- Remaining: migrate more editor-adjacent state and panel behavior out of the legacy coordinator so the catalog workflow no longer depends on direct DOM querying for the broader side-panel experience.
- Static export, dynamic live-build mode, and admin catalog-editing mode all continue to run from one application codebase.
- Runtime mode and capability checks are now centralized in a shared adapter module instead of being spread through multiple legacy helpers.
- Scene, atlas, catalog, and NPC-spawner data access now flow through the shared adapter boundary.
- This makes the read-only versus writable split explicit and reduces the risk of accidentally leaking admin-only paths into static mode.
- The old public coordinator files (`app.js`, DOM registry, UI control wrapper, map catalog UI wrapper, modal wrapper, and public state module) have been removed.
- The active controller now lives in `map_renderer/src/vue/controller/renderer-app.js` and is loaded from the Vue app shell.
### Phase 4 Progress
- The Vue tooltip component now owns the pinned tooltip and catalog editor presentation.
- Catalog editing controls are no longer assembled as legacy tooltip HTML strings.
- The legacy renderer still prepares tooltip metadata and executes save/rebuild actions, but those actions are now invoked from Vue through a bridge rather than direct listeners on generated DOM.
- Static mode remains read-only because the runtime adapter and site config still gate write capability before the editor can appear.
- The static export base shell now comes from the Vue build output rather than the deprecated source public shell.
The best outcome here is not a perfect one-shot rewrite. It is a controlled migration that keeps the current app working, makes the static and dynamic deployment split explicit, and steadily moves the renderer toward a maintainable Vue architecture.
The next meaningful milestone is no longer “get Vue on the page.” That is already done. The next milestone is to keep shrinking the legacy coordinator by moving shared viewer state, side-panel workflows, and editor affordances into Vue composables and components without regressing the atlas-scene viewer or the admin-only catalog workflow.