18 KiB
Vue Refactor Plan
Purpose
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.jscurrently 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.jsonalready 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.
Current State Re-evaluation
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
innerHTMLtooltip 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.jsentrypoint 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.
Non-Negotiable Requirements
- Keep a static deployable build that can run from committed artifacts with no file-system write capability.
- Keep a dynamic local build that can edit the Catalog and write changes back to disk.
- Preserve the existing viewer workflow during migration so the app remains usable at every step.
- Keep catalog editing opt-in and unavailable in static deployments.
- 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:
- 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.
- 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.
- 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.
- 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.jsresponsibilities. - 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.
- Ensure static mode cannot accidentally call writable endpoints.
- Ensure dynamic mode can still perform all current editor actions.
Deliverable:
- The same Vue UI can run in both modes without leaking write capability into static builds.
Phase 3 Status
- 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 oldsrc/public/app.jspath. - 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.
Phase 4: Migrate Catalog editing carefully
Goal: preserve catalog edit power while moving to Vue.
- Port the current catalog editor interactions into Vue components.
- Keep edit forms, validation, and confirmation behavior intact.
- Preserve undo or session history behavior if it exists in the current app.
- Verify that write operations remain local-only and mode-gated.
- Confirm that static builds either hide the edit UI or render it inert.
Deliverable:
- Catalog editing works in dynamic mode and is unreachable in static mode.
Phase 4 Status
- 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
innerHTMLgeneration. - 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.
Phase 5: Clean up the old entrypoint
Goal: remove dead code and simplify maintenance.
- Delete or shrink the legacy procedural bootstrap code only after feature parity is reached.
- Move any remaining helpers into reusable modules or composables.
- Remove duplicate state management paths.
- Keep the public API surface stable where external scripts or tests depend on it.
Deliverable:
- Vue owns the app; the old entrypoint is no longer the main coordination layer.
Suggested Component Breakdown
The exact component names can change, but the refactor should likely include:
- AppShell: top-level mode detection, configuration, and page layout.
- MapSelector: map and game selection, navigation, and load triggers.
- ViewerViewport: canvas or scene host, pointer handling, and zoom state.
- EditorToolbar: view toggles, overlays, and map display controls.
- CatalogPanel: catalog browsing, download buttons, and edit affordances.
- CatalogEditModal: edit form and validation.
- StatusBar: loading, build, and error feedback.
- TooltipOverlay: pinned or hover inspection UI.
- DownloadActions: export buttons and static bundle downloads.
Data and State Boundaries
The refactor should make these boundaries explicit:
- Scene loading state: selected map, build progress, current assets, and error state.
- View state: zoom, pan, overlays, tooltip pinning, and selection.
- Catalog state: active game, catalog entries, edit permissions, and pending writes.
- Environment state: static versus dynamic mode, feature flags, and API base URLs.
Rule of thumb:
- If state is shared across multiple UI regions, it belongs in a composable or store.
- If state only matters inside one panel or modal, keep it local to that component.
Static Build Plan
The static build must remain a first-class deployment target.
- Keep the committed site output deployable without server writes.
- Make the Vue build emit a static artifact suitable for GitHub Pages.
- Ensure all catalog editing UI is absent or disabled in the static bundle.
- Keep download links and read-only inspection available.
- Preserve the existing export workflow so the static bundle can be regenerated from local source assets.
Dynamic Build Plan
The dynamic build must preserve local editing capability.
- Keep the local server as the source of truth for writable catalog changes.
- Preserve file write paths and validation rules.
- Continue to support local refresh or rebuild behavior after edits.
- Keep the editing workflow explicit so the writable mode cannot be mistaken for the static deploy.
Suggested Repo Changes
The migration will probably require these changes:
- Introduce a Vue source directory under
map_renderer/srcor a sibling frontend directory. - Add a Vue build pipeline and update package scripts.
- Split the current DOM utility modules into composables, services, and small components.
- Add a mode/config module that clearly states whether the app is static or dynamic.
- Update export scripts so the static site still writes the correct committed artifacts.
- Update the README with the new build and deployment flow once the migration is stable.
Risks
- The biggest risk is mixing UI migration with behavior changes and losing parity.
- The second risk is accidentally allowing edit controls into the static bundle.
- The third risk is over-centralizing state in one large Vue store and recreating the same maintainability problem in a new framework.
- The fourth risk is changing the export pipeline before the static and dynamic modes are fully separated.
Acceptance Criteria
The refactor is complete when all of the following are true:
- The app runs as a Vue application.
- The static bundle still deploys and remains read-only.
- The dynamic local build still supports Catalog editing and writes to disk.
- The main viewer flows match the current behavior closely enough that no feature regression is visible in normal use.
- The old procedural entrypoint is no longer the primary app coordinator.
- The codebase is easier to extend because UI, state, and data access are separated.
Recommended Order of Work
- Inventory the current app responsibilities and define component boundaries.
- Scaffold Vue and mount the existing app behind it.
- Extract the largest UI regions into Vue components.
- Introduce explicit static and dynamic data adapters.
- Port catalog editing into Vue while keeping write capability local-only.
- Remove the leftover procedural bootstrap code.
- Update the docs and deployment instructions.
Concrete Implementation Checklist
Phase 1 Checklist
- Add Vue 3 and Vite dependencies to the renderer package.
- Add Vite scripts for development and production builds without removing the existing Node server scripts.
- Create a new Vue entrypoint that mounts a root app shell into the current page layout.
- Keep the existing static HTML usable during the transition by preserving the current shell structure and mount target.
- Split the top-level UI into a Vue root component plus a small compatibility layer for existing state and API helpers.
- Keep read-only viewer behavior intact during the initial scaffold so the app still loads maps exactly as before.
- Confirm that static mode still hides all write-capable catalog controls.
- Confirm that dynamic mode still exposes catalog-edit affordances after the Vue shell is in place.
Phase 1 Exit Criteria
- The renderer can be built and served through Vue/Vite.
- The current viewer still loads and renders maps.
- The static deployment path remains read-only.
- The dynamic deployment path remains capable of catalog editing.
- The old entrypoint is no longer the only place that owns page-level structure.
Phase 1 Progress
- Vue 3 and Vite scripts have been added to the renderer package.
- A Vue shell now renders the existing viewer DOM structure.
- The Vue shell loads the legacy browser app after mount so the current viewer logic remains active.
- The Vue production build completes successfully.
- The local dev and admin launchers now wait for the Vue build output and then start the server on the Vue-preferred path.
Phase 2 Progress
- The Vue shell has been split into separate shell components for the side panel, viewport, and egg editor modal.
- The root Vue app now composes those feature components instead of keeping the entire shell in one template file.
- The default local runtime path now serves the Vue build when it exists, so the componentized shell is the main entry for dev and admin modes.
Phase 3 Progress
- 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.jsand 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.
Final Notes
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.