Fix boot sequence alignment, rate limiter memory leak, and docs gaps
- 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 <noreply@anthropic.com>
This commit is contained in:
21
README.md
21
README.md
@@ -8,7 +8,7 @@ Unified layout, styling, and JavaScript utilities for all LotusGuild web applica
|
|||||||
|
|
||||||
## Quick Start
|
## 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
|
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)
|
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:
|
Then load them in your HTML:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
@@ -386,6 +395,10 @@ lt.toast.error(msg, durationMs?)
|
|||||||
lt.toast.warning(msg, durationMs?)
|
lt.toast.warning(msg, durationMs?)
|
||||||
lt.toast.info(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 */
|
/* Modals */
|
||||||
lt.modal.open('modal-id')
|
lt.modal.open('modal-id')
|
||||||
lt.modal.close('modal-id')
|
lt.modal.close('modal-id')
|
||||||
@@ -424,9 +437,9 @@ lt.time.format(isoString) // locale datetime string
|
|||||||
/* Bytes */
|
/* Bytes */
|
||||||
lt.bytes.format(1234567) // "1.18 MB"
|
lt.bytes.format(1234567) // "1.18 MB"
|
||||||
|
|
||||||
/* Table utilities */
|
/* Table utilities — must be called explicitly (not auto-initialized) */
|
||||||
lt.tableNav.init('table-id') // j/k/Enter keyboard navigation
|
lt.tableNav.init('table-id') // j/k/Enter keyboard navigation on <tbody> rows
|
||||||
lt.sortTable.init('table-id') // click-to-sort on data-sort-key headers
|
lt.sortTable.init('table-id') // click-to-sort on <th data-sort-key> headers
|
||||||
|
|
||||||
/* Stats widget filtering */
|
/* Stats widget filtering */
|
||||||
lt.statsFilter.init() // wires data-filter-key clicks
|
lt.statsFilter.init() // wires data-filter-key clicks
|
||||||
|
|||||||
@@ -195,13 +195,13 @@ GANDALF (`style.css`, `.section-title`):
|
|||||||
| Slide direction | from bottom | from top | from right | from right (`translateX(30px)`) |
|
| 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` |
|
| Animation duration | `0.3s ease` | `0.3s ease-out` | `0.15s ease` | `0.2s ease-out` |
|
||||||
| Auto-dismiss | 3500ms | 3000ms | 3500ms | `3500ms` |
|
| 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) |
|
| Queue system | Yes (serialised) | No (stacks) | No (stacks) | Yes (serialised) |
|
||||||
|
|
||||||
**Change required in PULSE:** Move toast position to `bottom: 20px; right: 20px`.
|
**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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
8
base.js
8
base.js
@@ -298,11 +298,13 @@
|
|||||||
overlay.style.opacity = '1';
|
overlay.style.opacity = '1';
|
||||||
|
|
||||||
const name = (appName || 'TERMINAL').toUpperCase();
|
const name = (appName || 'TERMINAL').toUpperCase();
|
||||||
const bar = '═'.repeat(Math.max(0, 41 - name.length));
|
const titleStr = name + ' v1.0';
|
||||||
const pad = ' '.repeat(Math.max(0, Math.floor((39 - name.length) / 2)));
|
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 = [
|
const messages = [
|
||||||
'╔═══════════════════════════════════════════╗',
|
'╔═══════════════════════════════════════════╗',
|
||||||
'║' + pad + name + ' v1.0' + pad + '║',
|
'║' + ' '.repeat(leftPad) + titleStr + ' '.repeat(rightPad) + '║',
|
||||||
'║ BOOTING SYSTEM... ║',
|
'║ BOOTING SYSTEM... ║',
|
||||||
'╚═══════════════════════════════════════════╝',
|
'╚═══════════════════════════════════════════╝',
|
||||||
'',
|
'',
|
||||||
|
|||||||
@@ -189,13 +189,19 @@ function createRateLimit(opts) {
|
|||||||
const hits = new Map();
|
const hits = new Map();
|
||||||
|
|
||||||
return function rateLimit(req, res, next) {
|
return function rateLimit(req, res, next) {
|
||||||
const key = req.ip || 'unknown';
|
const key = req.ip || req.socket?.remoteAddress || 'unknown';
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const entry = hits.get(key) || { count: 0, reset: now + windowMs };
|
const entry = hits.get(key) || { count: 0, reset: now + windowMs };
|
||||||
|
|
||||||
if (now > entry.reset) {
|
if (now > entry.reset) {
|
||||||
entry.count = 0;
|
entry.count = 0;
|
||||||
entry.reset = now + windowMs;
|
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++;
|
entry.count++;
|
||||||
hits.set(key, entry);
|
hits.set(key, entry);
|
||||||
|
|||||||
Reference in New Issue
Block a user