diff --git a/js/app.js b/js/app.js index c5d3d36..097084c 100644 --- a/js/app.js +++ b/js/app.js @@ -199,31 +199,38 @@ document.addEventListener('keydown', e => { } }); -// ── Bootstrap: fetch config.json then start ─────────────────────────────────── +// ── Bootstrap: load config.json, then start ─────────────────────────────────── +function bootstrapWithConfig(config) { + setOrgData(config.orgData); + setDefaultAffinities(config.defaultAffinities); + setOwnerColors(config.ownerColors); + // Stamp each task with its original config name as a stable identity key. + // User edits can rename tasks later, so this hidden key lets storage.js + // reconnect saved progress to the correct starter task on reload. + config.orgData.departments.forEach(dept => { + dept.tasks.forEach(task => { task._configName = task.name; }); + }); + initApp(); +} + document.addEventListener('DOMContentLoaded', () => { - // GitHub Pages serves this repo directly from source. Keep index.html loading - // this file as type="module", keep imports browser-compatible, and keep - // config.json reachable at the repo root for this fetch. - // - // config.json is the starter operating system for the app: departments, - // tasks, default role affinities, and owner colors all come from there. + // The production build (npm run build) inlines config.json into the page as + // window.__PM_OPS_CONFIG__ so the downloaded dist/ folder works by double- + // clicking index.html — fetch() of a local file is blocked by every modern + // browser's file:// CORS policy, so a runtime fetch can never work there. + // The raw source (GitHub Pages, npm start, python -m http.server) has no + // such global and falls through to the original fetch, unchanged. + if (window.__PM_OPS_CONFIG__) { + bootstrapWithConfig(window.__PM_OPS_CONFIG__); + return; + } + fetch('config.json') .then(r => { if (!r.ok) throw new Error(`config.json fetch failed: ${r.status}`); return r.json(); }) - .then(config => { - setOrgData(config.orgData); - setDefaultAffinities(config.defaultAffinities); - setOwnerColors(config.ownerColors); - // Stamp each task with its original config name as a stable identity key. - // User edits can rename tasks later, so this hidden key lets storage.js - // reconnect saved progress to the correct starter task on reload. - config.orgData.departments.forEach(dept => { - dept.tasks.forEach(task => { task._configName = task.name; }); - }); - initApp(); - }) + .then(bootstrapWithConfig) .catch(err => { console.error('PM Ops Map: could not load config.json —', err); document.getElementById('departments').innerHTML = diff --git a/package.json b/package.json index ef99081..253ebc0 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "pm-ops-map", "version": "1.0.0", "description": "A visual task ownership map for property management companies", + "license": "MIT", "private": true, "engines": { "node": ">=20.9" diff --git a/server/package.json b/server/package.json index d935743..25d211d 100644 --- a/server/package.json +++ b/server/package.json @@ -2,6 +2,7 @@ "name": "pm-ops-map-sync-server", "version": "1.0.0", "description": "Optional self-hosted sync server for PM Ops Map — lets a team share one workspace across devices without a full backend.", + "license": "MIT", "private": true, "main": "index.js", "engines": { diff --git a/webpack.config.prod.js b/webpack.config.prod.js index 4ae6b63..01a8961 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -1,8 +1,37 @@ +const fs = require('fs'); +const path = require('path'); const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +// Downloading the ZIP and double-clicking index.html opens it over file://, +// where every modern browser's CORS policy blocks fetch() of local files — +// runtime `fetch('config.json')` can never work there, regardless of script +// type. This plugin inlines config.json as a global for the built HTML only +// (the raw source at the repo root is untouched and keeps using fetch(), for +// GitHub Pages / npm start / any real HTTP server) and swaps the entry +// script from type="module" to a plain deferred script — the webpack bundle +// has already been compiled down to a classic IIFE with no import/export +// left in it, so the module wrapper serves no purpose in the built output. +class InlineConfigForFileProtocolPlugin { + apply(compiler) { + compiler.hooks.compilation.tap('InlineConfigForFileProtocolPlugin', (compilation) => { + HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( + 'InlineConfigForFileProtocolPlugin', + (data, cb) => { + const configJson = fs.readFileSync(path.resolve(__dirname, 'config.json'), 'utf8'); + data.html = data.html.replace( + '', + `\n` + ); + cb(null, data); + } + ); + }); + } +} + module.exports = merge(common, { mode: 'production', plugins: [ @@ -10,6 +39,7 @@ module.exports = merge(common, { template: './index.html', inject: false, }), + new InlineConfigForFileProtocolPlugin(), new CopyPlugin({ patterns: [ { from: 'config.json', to: 'config.json' },