From 997450aaf1aae5349aa2bb8fdc37d23080b9cae9 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 14 Mar 2026 21:27:31 -0400 Subject: [PATCH] Fix boot sequence alignment, rate limiter memory leak, and docs gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - base.js: Fix boot sequence title centering — old formula was off by 1 char for odd-length app names (PULSE, GANDALF). Remove unused `bar` variable. New logic computes left/right padding independently to handle both even and odd title lengths correctly. - node/middleware.js: Prune expired rate-limit entries from Map when size exceeds 5000 to prevent unbounded memory growth. Also use req.socket?.remoteAddress as fallback for req.ip. - README.md: Document lt.beep() in the JS API section. Clarify Quick Start to distinguish core files from platform-specific helpers. Note that tableNav.init() and sortTable.init() require explicit calls. - aesthetic_diff.md: Correct §8 toast icon format — base.js uses bracketed symbols [✓][✗][!][i], not >> prefix as previously stated. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 21 +++++++++++++++++---- aesthetic_diff.md | 6 +++--- base.js | 10 ++++++---- node/middleware.js | 8 +++++++- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2d1e707..15d384f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Unified layout, styling, and JavaScript utilities for all LotusGuild web applica ## Quick Start -Copy three files into your app's static/public directory: +**Core files** (needed by every app): ``` base.css → all variables, components, animations, responsive rules @@ -16,6 +16,15 @@ base.js → toast, modal, tabs, CSRF, fetch helpers, keyboard shortcuts base.html → full component reference (static demo, not for production) ``` +**Platform-specific helpers** (one per backend): + +``` +php/layout.php → PHP base layout with CSP nonce + CSRF injection +python/auth.py → Flask @require_auth / @require_admin decorators +python/base.html → Jinja2 base template with block inheritance +node/middleware.js → Express requireAuth, csrfMiddleware, cspNonce, etc. +``` + Then load them in your HTML: ```html @@ -386,6 +395,10 @@ lt.toast.error(msg, durationMs?) lt.toast.warning(msg, durationMs?) lt.toast.info(msg, durationMs?) +/* Audio */ +lt.beep('success' | 'error' | 'info') // Web Audio API beep; silent-fails if unavailable +// Note: toasts automatically call lt.beep() — only call directly for non-toast events + /* Modals */ lt.modal.open('modal-id') lt.modal.close('modal-id') @@ -424,9 +437,9 @@ lt.time.format(isoString) // locale datetime string /* Bytes */ lt.bytes.format(1234567) // "1.18 MB" -/* Table utilities */ -lt.tableNav.init('table-id') // j/k/Enter keyboard navigation -lt.sortTable.init('table-id') // click-to-sort on data-sort-key headers +/* Table utilities — must be called explicitly (not auto-initialized) */ +lt.tableNav.init('table-id') // j/k/Enter keyboard navigation on rows +lt.sortTable.init('table-id') // click-to-sort on headers /* Stats widget filtering */ lt.statsFilter.init() // wires data-filter-key clicks diff --git a/aesthetic_diff.md b/aesthetic_diff.md index 468bd42..e093de0 100644 --- a/aesthetic_diff.md +++ b/aesthetic_diff.md @@ -195,13 +195,13 @@ GANDALF (`style.css`, `.section-title`): | Slide direction | from bottom | from top | from right | from right (`translateX(30px)`) | | Animation duration | `0.3s ease` | `0.3s ease-out` | `0.15s ease` | `0.2s ease-out` | | Auto-dismiss | 3500ms | 3000ms | 3500ms | `3500ms` | -| Icon format | `>> ` prefix | `✓/✗/ℹ` prefix (inline style) | `>> ` prefix | `>> ` prefix + icon in `.lt-toast-icon` | +| Icon format | `>> ` prefix | `✓/✗/ℹ` prefix (inline style) | `>> ` prefix | `[✓]` `[✗]` `[!]` `[i]` in `.lt-toast-icon` | | Queue system | Yes (serialised) | No (stacks) | No (stacks) | Yes (serialised) | **Change required in PULSE:** Move toast position to `bottom: 20px; right: 20px`. -Replace inline-style notification function with `.lt-toast` classes. +Replace inline-style `showTerminalNotification()` with `lt.toast.*` calls. The icon format in `base.js` uses bracketed symbols: `[✓]`, `[✗]`, `[!]`, `[i]` — matches the terminal aesthetic better than bare unicode symbols. -**Change required in GANDALF:** Already close — update animation to `slide-in-right` instead of `slide-in` (which slides from left in Gandalf's current implementation). +**Change required in GANDALF:** Already close — update animation to `slide-in-right` instead of `slide-in` (which slides from left in GANDALF's current implementation). Replace `showToast()` calls with `lt.toast.*` calls. --- diff --git a/base.js b/base.js index b872de6..6caf677 100644 --- a/base.js +++ b/base.js @@ -297,12 +297,14 @@ overlay.style.display = 'flex'; overlay.style.opacity = '1'; - const name = (appName || 'TERMINAL').toUpperCase(); - const bar = '═'.repeat(Math.max(0, 41 - name.length)); - const pad = ' '.repeat(Math.max(0, Math.floor((39 - name.length) / 2))); + const name = (appName || 'TERMINAL').toUpperCase(); + const titleStr = name + ' v1.0'; + const innerWidth = 43; + const leftPad = Math.max(0, Math.floor((innerWidth - titleStr.length) / 2)); + const rightPad = Math.max(0, innerWidth - titleStr.length - leftPad); const messages = [ '╔═══════════════════════════════════════════╗', - '║' + pad + name + ' v1.0' + pad + '║', + '║' + ' '.repeat(leftPad) + titleStr + ' '.repeat(rightPad) + '║', '║ BOOTING SYSTEM... ║', '╚═══════════════════════════════════════════╝', '', diff --git a/node/middleware.js b/node/middleware.js index 2761a40..fc09dfc 100644 --- a/node/middleware.js +++ b/node/middleware.js @@ -189,13 +189,19 @@ function createRateLimit(opts) { const hits = new Map(); return function rateLimit(req, res, next) { - const key = req.ip || 'unknown'; + const key = req.ip || req.socket?.remoteAddress || 'unknown'; const now = Date.now(); const entry = hits.get(key) || { count: 0, reset: now + windowMs }; if (now > entry.reset) { entry.count = 0; entry.reset = now + windowMs; + /* Prune expired entries periodically to prevent memory growth */ + if (hits.size > 5000) { + for (const [k, v] of hits) { + if (now > v.reset) hits.delete(k); + } + } } entry.count++; hits.set(key, entry);