Skip to main content

max / makenotwork

Add static JS convention: data-attribute bridges, HTMX re-init pattern Document the established pattern for extracting inline scripts to static JS files. Key rules: data-* attributes for server data, IIFEs with container checks, htmx:afterSwap for partial re-initialization. Updates CONTRIBUTING.md (full convention), CLAUDE.md (cross-reference), and todo.md (extraction phases). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-05 18:43 UTC
Commit: 83793047f3a49882c13cfa980fd8a10fc0518eeb
Parent: 2006499
3 files changed, +72 insertions, -4 deletions
M CLAUDE.md +3 -1
@@ -31,7 +31,9 @@ Monorepo: `server/`, `multithreaded/`, `pom/`, `mnw-cli/`, `shared/`. No root `[
31 31
32 32 ## Coding Patterns
33 33
34 - See `server/CONTRIBUTING.md` for route handler patterns, error handling, macros (`impl_str_enum!`, `define_pg_uuid_id!`), DB query style, HTMX response patterns, template conventions, migration rules, and CSRF details.
34 + See `server/CONTRIBUTING.md` for route handler patterns, error handling, macros (`impl_str_enum!`, `define_pg_uuid_id!`), DB query style, HTMX response patterns, template conventions, migration rules, CSRF details, and frontend performance rules.
35 +
36 + **JavaScript:** New JS goes in `server/static/` as static files, not inline in templates. Pass server data via `data-*` attributes. See `server/CONTRIBUTING.md` § Static JavaScript. See `server/docs/performance_philosophy.md` for the design principles behind these rules.
35 37
36 38 See `multithreaded/CONTRIBUTING.md` for MT-specific patterns (MNW OAuth, shared dep sync, internal HMAC API).
37 39
@@ -321,9 +321,71 @@ Prefer server-complete responses over client-side loading placeholders. The serv
321 321
322 322 Use `hx-trigger="revealed"` with a loading placeholder only when the data is expensive to fetch AND hidden behind a `<details>` element (e.g., 2FA status, passkey list). Never use loading placeholders for content that's visible on initial tab load.
323 323
324 - ### Inline Scripts
324 + ### Static JavaScript
325 325
326 - Keep inline `<script>` blocks under 20 lines. Extract larger scripts to static JS files in `server/static/`. Static files are cached by the browser; inline scripts are re-parsed on every HTMX fragment swap.
326 + JavaScript lives in `server/static/`, one file per feature area:
327 +
328 + ```
329 + static/
330 + mnw.js — core utilities (CSRF, toasts, tabs, shortcuts) — loaded globally
331 + upload.js — S3 upload (S3Uploader, initDropzone) — loaded globally
332 + passkey.js — WebAuthn registration/login
333 + insertions.js — clip management
334 + wizard.js — wizard navigation
335 + docs-search.js — doc search index
336 + item-details.js — bundle, section, tag management
337 + item-upload.js — audio + version upload flows
338 + blog-editor.js — blog save/autosave/publish
339 + style.css — main stylesheet
340 + wizard.css — wizard-specific styles
341 + ```
342 +
343 + Only `mnw.js`, `upload.js`, and `htmx.min.js` are loaded globally (in `base.html` / `_head_assets.html`). All other JS files are loaded via `{% block scripts %}` in the page that needs them.
344 +
345 + **Passing server data to static JS:** Use `data-*` attributes on the feature's container element. The JS file reads them on init.
346 +
347 + ```html
348 + <!-- In template -->
349 + <div id="audio-upload" data-item-id="{{ item.id }}">
350 + <!-- upload UI -->
351 + </div>
352 +
353 + <!-- In {% block scripts %} -->
354 + <script src="/static/item-upload.js"></script>
355 + ```
356 +
357 + ```javascript
358 + // static/item-upload.js
359 + (function() {
360 + var el = document.getElementById('audio-upload');
361 + if (!el) return;
362 + var itemId = el.dataset.itemId;
363 + // ...
364 + })();
365 + ```
366 +
367 + For complex structured data (JSON arrays/objects that can't fit in an attribute), use a minimal inline script:
368 +
369 + ```html
370 + <script>window.MNW = window.MNW || {}; window.MNW.pageData = { segments: {{ segments_json|safe }} };</script>
371 + <script src="/static/audio-player.js"></script>
372 + ```
373 +
374 + **HTMX partial re-initialization:** Static JS loaded in `{% block scripts %}` runs once on page load. For HTMX partials (tab content swapped dynamically), use `htmx:afterSwap` to re-initialize:
375 +
376 + ```javascript
377 + document.body.addEventListener('htmx:afterSwap', function(e) {
378 + if (e.detail.target.id === 'tab-content') {
379 + initMyFeature();
380 + }
381 + });
382 + ```
383 +
384 + **When inline is OK:** Under 20 lines, no template variables that could be data attributes, and tightly coupled to a single template's DOM structure (e.g., tab overflow close handler, single-use form validation).
385 +
386 + ### Inline CSS
387 +
388 + Page-specific `<style>` blocks in `{% block head %}` are acceptable when the styles are truly unique to that page. Shared patterns (form layouts, tables, status badges, cards) belong in `style.css`.
327 389
328 390 ### Information Density
329 391
@@ -35,7 +35,11 @@ Dashboard restructure complete (Phases 1-6 in todo_done.md). Tab layout: Project
35 35 - [x] Fix layout shift: add aspect-ratio to all images missing dimensions (item page, audio player, buy page, wizards)
36 36 - [x] Replace `window.location.reload()` with HTMX tab re-fetch on item settings (4x), item details (1x), user profile (2x)
37 37 - [x] Remove "Loading..." placeholder text from 2FA and passkey sections (empty until revealed)
38 - - [ ] Extract heavy inline scripts to static JS — item_details.html (278 lines), upload handlers (~250 lines combined). Requires data-attribute refactor for template variables
38 + - [x] Add static JS convention to CONTRIBUTING.md — data-attribute bridges, IIFE pattern, htmx:afterSwap re-init, CLAUDE.md updated
39 + - [ ] Extract item-details.js (~350 lines) — bundle, section, tag management from item_details.html. Uses htmx:afterSwap + event delegation
40 + - [ ] Extract item-upload.js (~220 lines) — merge audio + version upload from partials. Same re-init pattern
41 + - [ ] Extract blog-editor.js (~100 lines) — from dashboard-blog-editor.html. Full page, no re-init needed
42 + - [ ] (Deferred) Extract audio-player.js (~450 lines) — complex state machine, full page load only, low priority
39 43
40 44 #### Discoverability
41 45 - [ ] Add Media Library access from content editors — "Insert Image" button in blog/item editors