Add design spec for JS module split
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
11767f147a
commit
d28f87a3c4
1 changed files with 116 additions and 0 deletions
116
docs/superpowers/specs/2026-05-08-js-module-split-design.md
Normal file
116
docs/superpowers/specs/2026-05-08-js-module-split-design.md
Normal file
|
|
@ -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
|
||||||
|
<!-- before -->
|
||||||
|
<script src="{{ asset('app.js') }}"></script>
|
||||||
|
|
||||||
|
<!-- after -->
|
||||||
|
<script type="module" src="{{ asset('js/app.js') }}"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
`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 `<script type="module">` when all modules are ready
|
||||||
|
4. Delete old `public/app.js`
|
||||||
Loading…
Reference in a new issue