diff --git a/docs/superpowers/specs/2026-05-08-js-module-split-design.md b/docs/superpowers/specs/2026-05-08-js-module-split-design.md new file mode 100644 index 0000000..7c86512 --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-js-module-split-design.md @@ -0,0 +1,116 @@ +# JS Module Split Design + +## Goal + +Split `public/app.js` (1025 lines, single file) into ES modules under `public/js/` — no build step, no npm. Modernize `var` to `const`/`let` in the process. + +## Module Structure + +``` +public/js/ + state.js shared mutable state object + i18n.js translations, locale logic + api.js fetch wrapper, loadGoals(), saveGoal() + goals.js pure goal calculations (calc, tOff, dcls, …) + ui.js tpl(), showToast(), overlay helpers, updateHeader() + auth.js login/register/password screens + sheets.js openNew(), openData(), openAdmin() + render.js buildCard(), render(), wire() + app.js entry point: init, URL params, stopwatch, midnight timer +``` + +## State Management + +Single exported object in `state.js`. All modules import and mutate it directly: + +```js +// state.js +export const state = { + goals: [], + userName: '', + isAdmin: false, + prefs: {}, + selDay: {}, + addAmt: {}, + renamingId: null, + renameVal: '', + collapsed: {}, + TODAY: new Date(), +}; +``` + +Replaces all global `var` declarations at the top of `app.js`. Mutations like `goals = data` become `state.goals = data`. + +## Dependency Graph + +``` +state.js ← all modules +i18n.js ← goals.js, ui.js, auth.js, sheets.js, render.js, app.js +api.js ← auth.js, sheets.js, app.js +goals.js ← render.js, sheets.js +ui.js ← auth.js, sheets.js, render.js +auth.js ← sheets.js, app.js +render.js ← app.js +``` + +## Circular Import Solution + +`api.js` previously called `showLogin()` on 401 — creating a circular dependency with `auth.js`. Resolved via a Custom Event: + +```js +// api.js — on 401: +document.dispatchEvent(new CustomEvent('session-expired')); + +// app.js — on startup: +document.addEventListener('session-expired', () => showLogin()); +``` + +Events are used only for this cross-cutting concern. All other communication is via direct imports. + +## const/let Conversion + +- ES modules run in strict mode automatically +- `var` → `const` where the binding is never reassigned +- `var` → `let` for loop counters and mutated locals +- State re-assignments (`goals = newArray`) → `state.goals = newArray` +- `TODAY` moves into `state.TODAY`, updated in `scheduleMidnight()` and `visibilitychange` + +## HTML Change + +`templates/app.html.twig`: + +```html + + + + + +``` + +`public/app.js` is deleted after the migration is complete. + +## File Size Estimates + +| File | Est. lines | +|---|---| +| state.js | ~15 | +| i18n.js | ~185 | +| api.js | ~35 | +| goals.js | ~60 | +| ui.js | ~35 | +| auth.js | ~130 | +| sheets.js | ~135 | +| render.js | ~185 | +| app.js | ~60 | +| **Total** | **~840** | + +(Reduction due to removed comments and tighter const/let style.) + +## Migration Strategy + +One module at a time, keeping the app functional throughout: + +1. Create `public/js/` directory +2. Write modules in dependency order: state → i18n → api → goals → ui → auth → sheets → render → app +3. Switch HTML to `