Files
web_template/aesthetic_diff.md
Jared Vititoe 997450aaf1 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>
2026-03-14 21:27:31 -04:00

438 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LotusGuild Aesthetic Diff — Convergence Guide
Cross-app analysis of every divergence between Tinker Tickets (PHP),
PULSE (Node.js), and GANDALF (Flask) that prevents a unified aesthetic.
Each section lists the current state in each app, then the **target state**
from the unified design system (`web_template/base.css`).
---
## 1. CSS Custom Property Names
The biggest source of divergence. Gandalf uses entirely different variable names.
| Property | Tinker Tickets | PULSE | GANDALF | **Target** |
|---|---|---|---|---|
| Main background | `--bg-primary: #0a0a0a` | `--bg-primary: #0a0a0a` | `--bg: #0a0a0a` | `--bg-primary` |
| Surface | `--bg-secondary: #1a1a1a` | `--bg-secondary: #1a1a1a` | `--bg2: #1a1a1a` | `--bg-secondary` |
| Raised surface | `--bg-tertiary: #2a2a2a` | `--bg-tertiary: #2a2a2a` | `--bg3: #2a2a2a` | `--bg-tertiary` |
| Primary green | `--terminal-green: #00ff41` | `--terminal-green: #00ff41` | `--green: #00ff41` | `--terminal-green` |
| Green tint | `--terminal-green-dim` | `--terminal-green-dim` | `--green-dim: rgba(0,255,65,.15)` | `--terminal-green-dim` |
| Amber | `--terminal-amber: #ffb000` | `--terminal-amber: #ffb000` | `--amber: #ffb000` | `--terminal-amber` |
| Amber tint | `--terminal-amber-dim` | `--terminal-amber-dim` | `--amber-dim` | `--terminal-amber-dim` |
| Cyan | `--terminal-cyan: #00ffff` | `--terminal-cyan: #00ffff` | `--cyan: #00ffff` | `--terminal-cyan` |
| Red | `--terminal-red: #ff4444` | `--terminal-red: #ff4444` | `--red: #ff4444` | `--terminal-red` |
| Body text | `--text-primary` | `--text-primary` | `--text` | `--text-primary` |
| Dim text | `--text-secondary` | `--text-secondary` | `--text-dim` | `--text-secondary` |
| Muted text | `--text-muted: #00bb33` | `--text-muted: #008822` | `--text-muted: #00bb33` | `--text-muted: #00bb33` |
| Border | `--border-color` | `--border-color` | `--border: rgba(0,255,65,.35)` | `--border-color` |
| Glow (green) | `--glow-green` | `--glow-green` | `--glow` | `--glow-green` |
| Glow (amber) | `--glow-amber` | `--glow-amber` | `--glow-amber` | `--glow-amber` ✓ |
| Font | `--font-mono` | `--font-mono` | `--font` | `--font-mono` |
### Fix for GANDALF (`style.css`)
Add these aliases at the top of `:root` (keeps existing rules working):
```css
:root {
/* Aliases to match unified naming */
--bg-primary: var(--bg);
--bg-secondary: var(--bg2);
--bg-tertiary: var(--bg3);
--terminal-green: var(--green);
--terminal-green-dim:var(--green-dim);
--terminal-amber: var(--amber);
--terminal-amber-dim:var(--amber-dim);
--terminal-cyan: var(--cyan);
--terminal-red: var(--red);
--text-primary: var(--text);
--text-secondary: var(--text-dim);
--border-color: var(--border);
--glow-green: var(--glow);
--font-mono: var(--font);
--text-muted: #00bb33; /* override GANDALF's #008822 — too dark */
}
```
Then migrate GANDALF's new code to use unified names. Remove aliases on next major refactor.
---
## 2. Border Width & Style
| Context | Tinker Tickets | PULSE | GANDALF | **Target** |
|---|---|---|---|---|
| Standard card / panel | `2px solid` | `2px solid` | `1px solid` | `2px solid` |
| Modal | `3px double` | `3px double` | `1px solid` | `3px double` |
| Table outer | `2px solid` | — | none | `2px solid` |
| Input fields | `2px solid` | `2px solid` | `1px solid` | `2px solid` |
| Button | `2px solid` | `2px solid` | `1px solid` (`.btn-sm`) | `2px solid` |
**Change required in GANDALF:** Increase `.modal`, `.host-card`, `.data-table`, `.form-group input/select`, `.btn-sm` border widths from `1px` to `2px`. The `1px` borders make elements look fragile compared to the other apps.
```css
/* GANDALF style.css — search & replace */
.modal { border: 1px solid border: 3px double }
.host-card { border: 1px solid border: 2px solid }
.form-group input,
.form-group select { border: 1px solid border: 2px solid }
.btn-sm { border: 1px solid border: 2px solid }
```
---
## 3. Button Pattern
| Aspect | Tinker Tickets | PULSE | GANDALF | **Target** |
|---|---|---|---|---|
| Bracket decoration | `[ text ]` via `::before`/`::after` | `[ text ]` via `::before`/`::after` | `> text` (primary only), no brackets on most buttons | `[ text ]` via `::before`/`::after` |
| Hover transform | `translateY(-2px)` | `translateY(-2px)` | none | `translateY(-2px)` |
| Hover animation | `pulse-glow-box 1.5s infinite` | none | none | none (lift is sufficient) |
| Padding (standard) | `10px 20px` | `12px 24px` | `6px 14px` (`.btn`) | `10px 20px` |
| Padding (small) | `5px 10px` | `6px 12px` | `2px 8px` | `5px 10px` |
| Font size | `0.9rem` | `1em` | `0.72em0.8em` | `0.9rem` |
| Text transform | uppercase | uppercase | uppercase | uppercase |
**Change required in GANDALF:** Add `::before`/`::after` bracket decorations to all `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-danger` classes. Add `translateY(-2px)` hover transform. Increase padding.
```css
/* GANDALF — add to existing .btn rules */
.btn::before { content: '[ '; }
.btn::after { content: ' ]'; }
.btn:hover { transform: translateY(-2px); }
/* Adjust .btn-primary::before to '> ' instead of '[ ' for visual differentiation */
.btn-primary::before { content: '> '; }
```
---
## 4. Glow Definition Consistency
| App | Green glow | Amber glow |
|---|---|---|
| Tinker Tickets | `0 0 5px #00ff41, 0 0 10px #00ff41, 0 0 15px #00ff41` (3-layer solid) | `0 0 5px #ffb000, 0 0 10px #ffb000, 0 0 15px #ffb000` |
| PULSE | Identical to Tinker Tickets | Identical |
| GANDALF | `0 0 5px #00ff41, 0 0 10px rgba(0,255,65,.4)` (2-layer, rgba 2nd) | `0 0 5px #ffb000, 0 0 10px rgba(255,176,0,.4)` |
GANDALF's 2-layer glow is slightly softer. Both look fine, but they appear different on the same screen if pages are compared side-by-side.
**Change required in GANDALF:** Update glow definitions to match the 3-layer solid stack:
```css
/* GANDALF style.css */
:root {
--glow: 0 0 5px #00ff41, 0 0 10px #00ff41, 0 0 15px #00ff41;
--glow-xl: 0 0 8px #00ff41, 0 0 16px #00ff41, 0 0 24px #00ff41, 0 0 32px rgba(0,255,65,.5);
--glow-amber: 0 0 5px #ffb000, 0 0 10px #ffb000, 0 0 15px #ffb000;
}
```
---
## 5. Section Header Pattern
| App | Syntax | Example |
|---|---|---|
| Tinker Tickets | `╠═══ TITLE ═══╣` | Symmetric, double-bar bookends |
| PULSE | `═══ TITLE ═══` (via h3::before/after) | No `╠` character |
| GANDALF | `╠══ TITLE` (one-sided) | Only left bookend, no right |
**Change required in PULSE and GANDALF:** Standardise to `╠═══ TITLE ═══╣`.
PULSE (`index.html`, all `h3::before`/`h3::after`):
```css
/* PULSE: update h3 pseudo-elements */
h3::before { content: '╠═══ '; color: var(--terminal-green); }
h3::after { content: ' ═══╣'; color: var(--terminal-green); }
```
GANDALF (`style.css`, `.section-title`):
```css
/* GANDALF: add right bookend */
.section-title::after { content: ' ═══╣'; color: var(--green); }
```
---
## 6. Modal Border Style
| App | Border | Box shadow |
|---|---|---|
| Tinker Tickets | `3px double var(--terminal-green)` | none |
| PULSE | `3px double var(--terminal-green)` | `0 0 30px rgba(0,255,65,.3)` |
| GANDALF | `1px solid var(--green)` | `0 0 30px rgba(0,255,65,.18)` |
**Change required in GANDALF:** Upgrade to `3px double` and add PULSE-style glow:
```css
.modal {
border: 3px double var(--green);
box-shadow: 0 0 30px rgba(0, 255, 65, 0.2), 0 8px 40px rgba(0,0,0,0.8);
}
```
---
## 7. Modal Corner Characters
| App | Top corners | Notes |
|---|---|---|
| Tinker Tickets | `╔ ╗` (double-line) | Matches `3px double` border |
| PULSE | `╔ ╗` (double-line) | Matches `3px double` border |
| GANDALF | `┌ ┐` (single-line) | Doesn't match — should be `╔ ╗` for modals |
**Change required in GANDALF:**
```css
.modal::before { content: '╔'; }
.modal::after { content: '╗'; }
```
---
## 8. Toast Position & Animation
| Aspect | Tinker Tickets | PULSE | GANDALF | **Target** |
|---|---|---|---|---|
| Position | `bottom: 1rem; right: 1rem` | `top: 80px; right: 20px` | `bottom: 20px; right: 20px` | `bottom: 1rem; right: 1rem` |
| 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 | `[✓]` `[✗]` `[!]` `[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 `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). Replace `showToast()` calls with `lt.toast.*` calls.
---
## 9. Table Cell Borders
| App | Approach |
|---|---|
| Tinker Tickets | `border: 1px solid var(--border-color)` on every `<td>` — full grid |
| PULSE | Minimal table usage; when present, `border-bottom` only |
| GANDALF | `border-collapse: collapse` + `border-bottom: 1px solid rgba(0,255,65,.08)` row-only |
Both approaches are valid for different use cases. The design system provides both:
- `.lt-table` → full-grid borders (Tinker Tickets style, simple data)
- `.lt-data-table` → row-only borders (GANDALF style, dense data)
**Action:** Migrate existing tables to the appropriate class. No visual breakage, just choose the right variant per context.
---
## 10. Form Label Colour
| App | Label colour |
|---|---|
| Tinker Tickets | `color: var(--terminal-green)` |
| PULSE | `color: var(--terminal-green)` |
| GANDALF | `color: var(--amber)` (amber labels) |
GANDALF's amber labels create a better visual hierarchy (labels stand out from field values). The unified design system adopts **amber labels** for all apps.
**Change required in Tinker Tickets and PULSE:**
```css
/* Tinker Tickets: assets/css/dashboard.css + ticket.css */
label, .filter-group h4 { color: var(--terminal-amber); text-shadow: var(--glow-amber); }
/* PULSE: index.html inline CSS */
label { color: var(--terminal-amber); }
```
---
## 11. Nav Link Active State
| Aspect | Tinker Tickets | PULSE (tabs) | GANDALF | **Target** |
|---|---|---|---|---|
| Active colour | amber | amber | amber | amber ✓ |
| Active background | `rgba(0,255,65,.08)` | `rgba(0,255,65,.2)` | `rgba(0,255,65,.07)` | `rgba(255,176,0,.15)` (amber tint) |
| Active border | green | amber | `border-color: var(--border)` (invisible) | amber |
| `[ ]` brackets | on `.btn` but not nav | on `.tab` | on `.nav-link` | on nav links |
**Change required in Tinker Tickets:** Add `[ ]` bracket decoration to nav links to match GANDALF. Currently Tinker Tickets has plain nav links without brackets.
**Change required in GANDALF:** Use amber tint background on active state instead of green tint:
```css
.nav-link.active {
background: var(--amber-dim); /* was: var(--green-dim) */
border-color: var(--amber);
}
```
---
## 12. `text-muted` Colour Value
| App | Value | Contrast on #0a0a0a |
|---|---|---|
| Tinker Tickets | `#00bb33` | ~4.8:1 |
| PULSE | `#008822` | ~2.9:1 ✗ WCAG AA fail |
| GANDALF | `#00bb33` | ~4.8:1 |
**Change required in PULSE:** Update `--text-muted` from `#008822` to `#00bb33` to pass WCAG AA contrast.
```css
/* PULSE index.html :root */
--text-muted: #00bb33; /* was: #008822 */
```
---
## 13. Boot Sequence — Presence & Format
| App | Boot sequence | App name in banner |
|---|---|---|
| Tinker Tickets | Yes — `showBootSequence()` in `DashboardView.php` | "TINKER TICKETS TERMINAL v1.0" |
| PULSE | Yes — `showBootSequence()` in `index.html` | "PULSE ORCHESTRATION TERMINAL v1.0" |
| GANDALF | **No** | — |
**Change required in GANDALF:** Add boot sequence overlay to `base.html`.
```html
<!-- Add to base.html <body> -->
<div id="lt-boot" class="lt-boot-overlay" data-app-name="GANDALF" style="display:none">
<pre id="lt-boot-text" class="lt-boot-text"></pre>
</div>
```
Plus add `lt.boot.run('GANDALF');` in `app.js` or inline at end of `base.html`.
---
## 14. Status Badge Format
| App | Format | Example |
|---|---|---|
| Tinker Tickets | `[● Open]``::before`/`::after` brackets | Brackets are pseudo-elements |
| PULSE | `[● Online]` — same pattern | Same |
| GANDALF | `.chip::before { content: '['; }` + `.chip::after { content: ']'; }` | Same pattern — different class names |
The pattern is consistent. The only issue is **class names** differ:
| Component | Tinker Tickets | PULSE | GANDALF |
|---|---|---|---|
| Full status badge | `.status-Open`, `.status-Closed` | `.status.online`, `.status.failed` | `.chip-critical`, `.chip-ok` |
| Small badge | — | `.badge` | `.badge`, `.chip` |
**Standardise to** `.lt-status-*` (full badge) + `.lt-chip-*` (compact) + `.lt-badge-*` (inline label) going forward. Existing class names can remain as app-internal aliases.
---
## 15. Scanline Effect Differences
All three apps have the same scanline `body::before` and data-stream `body::after`. Minor differences:
| Aspect | Tinker Tickets | PULSE | GANDALF |
|---|---|---|---|
| Scanline opacity | `rgba(0,0,0,0.15)` | `rgba(0,0,0,0.15)` | `rgba(0,0,0,.13)` |
| Flicker delay | `30s` | `30s` | `45s` |
| Data stream position | `bottom:10px; right:10px` | `bottom:10px; right:10px` | `bottom:10px; right:14px` |
These are minor. **Standardise to:** opacity `0.15`, flicker delay `30s`, position `bottom:10px; right:14px`.
---
## 16. Hover Transform on Cards / Items
| App | Card hover | List item hover |
|---|---|---|
| Tinker Tickets | `translateY(-2px)` + glow | `translateY(-2px)` |
| PULSE | `translateY(-2px)` | `translateX(3px)` |
| GANDALF | `border-color` change only, no transform | `border-left-width` expansion |
**Standardise to:** Cards use `translateY(-2px)`. List/row items use `border-left-width` expansion (GANDALF approach, less disorienting for dense lists).
---
## 17. CSS Architecture — Inline vs. External
| App | CSS location |
|---|---|
| Tinker Tickets | External: `assets/css/dashboard.css` + `ticket.css` |
| PULSE | **All inline** in `index.html` (`<style>` tag, ~800 lines) |
| GANDALF | External: `static/style.css` |
**Change required in PULSE:** Extract the `<style>` block from `index.html` into `public/style.css`. This enables:
- Browser caching
- Cache-busting with `?v=YYYYMMDD`
- Editor syntax highlighting
- Easier diff when updating to unified base.css
---
## 18. JavaScript Toast Implementation
| Aspect | Tinker Tickets | PULSE | GANDALF |
|---|---|---|---|
| Function name | `showToast(msg, type, duration)` | `showTerminalNotification(msg, type)` | `showToast(msg, type)` |
| Uses CSS classes | Yes (`.terminal-toast`) | No (inline styles) | Yes (`.toast`) |
| Queue system | Yes | No | No |
| Plays audio | No | Yes (Web Audio) | No |
**Standardise all three** to use `lt.toast.*` from `base.js`. The function is already a superset of all three implementations.
Required changes:
- Tinker Tickets: Replace `showToast()` in `toast.js` with `lt.toast.*` calls
- PULSE: Replace `showTerminalNotification()` in `index.html` with `lt.toast.*` calls
- GANDALF: Replace `showToast()` in `app.js` with `lt.toast.*` calls
---
## 19. CSRF Implementation
| App | Method | Token location |
|---|---|---|
| Tinker Tickets | PHP `CsrfMiddleware::getToken()``X-CSRF-Token` header | `window.CSRF_TOKEN` via inline script |
| PULSE | No CSRF tokens (session cookies + API key auth) | — |
| GANDALF | No CSRF tokens (Authelia `SameSite=Strict`) | — |
No breaking changes needed. `lt.csrf.headers()` gracefully returns `{}` when `window.CSRF_TOKEN` is not set, so PULSE and GANDALF are unaffected.
---
## 20. Responsive Design Coverage
| App | Mobile-responsive? | Notes |
|---|---|---|
| Tinker Tickets | Yes — extensive breakpoints, card view below 1400px | Most complete |
| PULSE | Partial — `auto-fit` grid, 90vw modals | No nav changes on mobile |
| GANDALF | Partial — grid wraps, no explicit breakpoints documented | Nav becomes scrollable |
**Change required in PULSE and GANDALF:** Add mobile nav handling (hamburger / collapsible), hide `.lt-nav` below 768px, ensure tables are horizontally scrollable on mobile.
---
## Priority Order for Convergence
| Priority | Item | Effort | Impact |
|---|---|---|---|
| 🔴 High | **GANDALF: CSS variable renaming** (§1) | Medium | Eliminates all naming confusion |
| 🔴 High | **PULSE: Extract inline CSS to file** (§17) | Low | Enables sharing base.css |
| 🔴 High | **All: Standardise toast to `lt.toast`** (§18) | Low | Consistent UX across apps |
| 🟡 Medium | **GANDALF: Border widths 1px→2px** (§2) | Low | Visual parity |
| 🟡 Medium | **GANDALF: Modal → `3px double`** (§6) | Low | Visual parity |
| 🟡 Medium | **PULSE: Fix `--text-muted` contrast** (§12) | Trivial | Accessibility |
| 🟡 Medium | **All: Section headers → `╠═══ X ═══╣`** (§5) | Low | Consistent branding |
| 🟡 Medium | **GANDALF: Add boot sequence** (§13) | Low | Brand consistency |
| 🟢 Low | **GANDALF: Button `[ ]` brackets + hover lift** (§3) | Low | Visual consistency |
| 🟢 Low | **TT + PULSE: Amber form labels** (§10) | Low | Better hierarchy |
| 🟢 Low | **Scanline exact values** (§15) | Trivial | Micro-consistency |
| 🟢 Low | **PULSE: Toast position bottom-right** (§8) | Trivial | UX consistency |
| 🟢 Low | **GANDALF: Glow 2-layer → 3-layer** (§4) | Trivial | Visual consistency |
---
## What Does NOT Need to Change
These differences are intentional or platform-specific and should be preserved:
- **GANDALF's compact font sizes** (`.7em` for table headers) — appropriate for dense network data
- **PULSE's tab-based navigation** — appropriate for single-page app structure
- **Tinker Tickets' full table cell borders** — appropriate for ticket data with many columns
- **PULSE's WebSocket real-time updates** — architecture difference, not aesthetic
- **GANDALF's collapse state stored in sessionStorage** vs. Tinker Tickets using CSS class toggle — both work
- **Status colour values for tickets vs. network events** — apps use different semantic colours for their domain (ticket statuses vs. UP/DOWN states)
- **Priority colour scale** — only Tinker Tickets uses P1P5; the other apps don't need it