7 Commits

Author SHA1 Message Date
efiten 11d2026bb1 feat(startup): hot startup — load hotStartupHours synchronously, fill retentionHours in background (#1187)
Closes #1183

## Summary

- Adds `packetStore.hotStartupHours` config key (float64, default 0 =
disabled). When set, `Load()` loads only that many hours of data
synchronously, reducing startup time on large DBs. Background goroutine
fills the remaining `retentionHours` window in daily chunks after
startup completes.
- A background goroutine (`loadBackgroundChunks`) fills the remaining
`retentionHours` window in daily chunks after startup completes.
Analytics indexes are rebuilt once at the end.
- `QueryPackets` and `QueryGroupedPackets` check `oldestLoaded` and fall
back to `db.QueryPackets()` for any query whose `Since`/`Until` predates
the in-memory window — covering days 8–30 permanently (beyond
`retentionHours`) and the background-fill gap during startup.
- `/api/perf` gains `hotStartupHours`, `backgroundLoadComplete`, and
`backgroundLoadProgress` fields inside `packetStore` so operators can
monitor the fill.

### Drive-by fixes

- E2E: added `gotoPackets` navigation helper used across packet-related
tests
- E2E: rewrote stripe assertion to check per-row stripe parity rather
than a fragile computed-style comparison
- E2E: theme test updated to use `#/home` as the initial route (was
`#/`)
- `db.go`: removed the RFC3339→unix-timestamp subquery path in
`buildTransmissionWhere`; `t.first_seen` is now always compared directly
as a string for both RFC3339 and non-RFC3339 inputs

## Configuration

```json
"packetStore": {
  "retentionHours": 168,
  "hotStartupHours": 24
}
```

`hotStartupHours: 0` (default) preserves existing behavior exactly.
Recommended for large DBs to reduce startup time; set to 0 to disable
(loads full retentionHours at startup, legacy behavior).

## Test plan

- [x] `TestHotStartupConfig_Clamp` — clamping when `hotStartupHours >
retentionHours`
- [x] `TestHotStartupConfig_ZeroIsDisabled` — zero leaves feature
disabled
- [x] `TestHotStartup_LoadsOnlyHotWindow` — only hot-window packets in
memory after `Load()`
- [x] `TestHotStartup_DisabledWhenZero` — all retention packets loaded
when disabled
- [x] `TestHotStartup_loadChunk_AddsOlderData` — chunk merges correctly,
ASC order maintained
- [x] `TestHotStartup_BackgroundFillsToRetention` — background goroutine
fills to `retentionHours`
- [x] `TestHotStartup_ChunkErrorRecovery` — chunk SQL failure logged and
skipped, loop terminates
- [x] `TestHotStartup_SQLFallback_TriggeredForOldDate` — query before
`oldestLoaded` routes to SQL
- [x] `TestHotStartup_SQLFallback_NotTriggeredForRecentDate` — recent
query stays in-memory
- [x] `TestHotStartup_PerfStats` — new fields present in
`GetPerfStoreStats()` (backs the perf endpoint)
- [x] `TestHotStartup_PerfStoreHTTP` — HTTP-level: GET /api/perf returns
`hotStartupHours`, `backgroundLoadComplete`, `backgroundLoadProgress` in
`packetStore`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: openclaw-bot <bot@openclaw.local>
Co-authored-by: CoreScope Bot <bot@corescope.local>
2026-05-15 22:46:25 -07:00
Kpa-clawbot b4f186af19 fix(#1062): gesture system — swipe rows, tabs, slide-over dismiss (#1185)
Red commit: bbb98cf81a (CI run: pending —
see Checks tab)

Fixes #1062. Parent: #1052.

## Gesture system

Adds touch-gesture handling on phones (≤768px):

1. **Swipe-left on a packets/nodes/observers row** → reveals row-action
overlay (trace, filter, copy hash). Threshold: 24% of row width OR 80px.
Sub-threshold = visual peek that snaps back.
2. **Horizontal swipe on the bottom-nav strip** → advances tabs in TAB
order from `bottom-nav.js`. Packets ↔ Live ↔ Map etc.
3. **Swipe-down on a slide-over panel** → calls
`window.SlideOver.close()`.

## Hard constraints met

- **Pointer Events ONLY** — no `touchstart`/`touchend` mixing.
`setPointerCapture` for tracking continuity.
- **Axis-lock** — direction committed in first 8–12px movement. Vertical
scroll is never blocked unless we explicitly committed to a horizontal
swipe. `body { touch-action: pan-y }` so the browser owns vertical
natively.
- **Leaflet exclusion** — handlers early-bail on
`e.target.closest('.leaflet-container')` so pinch/pan on the map tab are
untouched.
- **Singleton pattern** — module-scoped `__touchGestures1062InitCount`
guard. Document-level pointer listeners registered exactly once even if
the script loads multiple times (mirrors the #1180 fix class).
- **prefers-reduced-motion** — animations have `transition-duration: 0s`
under the media query; gestures still trigger, snaps are instant.

## E2E

`test-gestures-1062-e2e.js` — Playwright with synthesized PointerEvents
(page.touchscreen unreliable in headless for axis-locked custom
handlers). Wired into the deploy.yml matrix.

E2E assertion added: test-gestures-1062-e2e.js:120 (overlay-visible
after left-swipe), :201 (tab advance), :219 (Leaflet exclusion), :247
(slide-over dismiss).

---------

Co-authored-by: openclaw-bot <bot@openclaw>
Co-authored-by: OpenClaw Bot <bot@openclaw.dev>
Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com>
Co-authored-by: clawbot <clawbot@users.noreply.github.com>
Co-authored-by: corescope-bot <bot@corescope.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>
2026-05-09 18:41:19 -07:00
Kpa-clawbot 05876b3a59 fix(#1173): replace #liveDot with packet-driven brand-logo node-pulse (#1177)
Red commit: PENDING (will update)

Fixes #1173.

Replaces the `#liveDot` WebSocket-connected indicator with a
packet-driven node-pulse animation on the brand logo's two inner
circles.

## Behavior (locked per issue spec)
- **Animation curve:** `ease-out` (default per open-question 1).
- **Rate cap:** 15/sec (66ms gap; default per open-question 2). Excess
triggers are dropped, never queued.
- **Direction:** alternates A→B / B→A across messages (aesthetic, not
semantic).
- **Idle ≥10s:** logo at full brightness, no animation.
- **Disconnected:** `.logo-disconnected` applies `filter: grayscale(0.6)
opacity(0.7)`.
- **`prefers-reduced-motion: reduce`:** single-step `.logo-pulse-blip`
on destination only.

## Implementation
- WS handler hook lives in `public/app.js` `connectWS()` (`ws.onmessage`
triggers `Logo.pulse()`; `ws.onopen`/`ws.onclose` toggle
`Logo.setConnected()`).
- `Logo` is a small IIFE in `app.js` that exposes
`window.__corescopeLogo` for E2E injection.
- All animation is pure CSS; JS only toggles `.logo-pulse-active` /
`.logo-pulse-blip` / `.logo-disconnected`. Colors come exclusively from
`--logo-accent` / `--logo-accent-hi` tokens.
- Two new classes (`.logo-node-a`, `.logo-node-b`) attached to inner
circles in both `.brand-logo` and `.brand-mark-only` SVGs so the mobile
mark animates too.

## `#liveDot` removal proof
```
$ grep -rn liveDot public/
(no output)
```

## E2E
- E2E assertion added: `test-logo-pulse-1173-e2e.js:54` and follows.
- Wired into the Playwright matrix in `.github/workflows/deploy.yml`
(mirrors PR #1168 pattern from commit `5442652`).
- Test injects synthetic pings via `window.__corescopeLogo.pulse({
synthetic: true })`; matches the existing harness style (no new WS-mock
pattern invented).

Red→green discipline preserved: the test commit lands first and CI fails
on assertion; the implementation commit follows.

---------

Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
Co-authored-by: corescope-bot <bot@corescope.local>
2026-05-08 20:25:42 -07:00
Kpa-clawbot cfd1903c6b fix(logo): default sage/teal brand colors, customizer mirrors accent (#1162)
## Summary

Restores sage/teal as default logo colors while preserving customizer
theming. Closes the gap from #1157 (closed) and the user's complaint
about lost two-tone.

Out-of-the-box, the navbar + hero CORE/SCOPE wordmarks now render the
brand-identity duotone — `#cfd9c9` (sage / fog) and `#2c8c8c` (teal /
water). When an operator picks a theme via the customizer (or sets a
custom accent color), the wordmark recolors to follow.

## Approach (Option C — decoupled defaults + customizer mirror)

- **`public/style.css` `:root`** — set `--logo-accent: #cfd9c9` and
`--logo-accent-hi: #2c8c8c` as literal defaults. Removes the previous
`var(--accent)` cascade so blue-by-default no longer leaks into the
brand mark.
- **`public/customize-v2.js`** — `applyTheme()`, the early-apply path,
and the live color-picker `input` handler now mirror
`themeSection.accent` → `--logo-accent` and `themeSection.accentHover` →
`--logo-accent-hi`.
- **`public/customize.js`** (legacy) — same mirroring in
`applyThemePreview()` and the early localStorage replay.
- **`.github/workflows/deploy.yml`** — adds the new e2e to the Chromium
batch.

This preserves `--accent` as the canonical app-wide accent token (no
other UI changes) while giving the logo its own brand-defaulted tokens
that the customizer still drives.

## Tests

Red → green commit pair on the branch.

- **NEW: `test-logo-default-sage-teal-e2e.js`** — gates both halves of
the contract:
1. Clean localStorage → navbar + hero CORE = `rgb(207, 217, 201)`, SCOPE
= `rgb(44, 140, 140)`.
2. Seeded `cs-theme-overrides` with red accent → navbar + hero recolor
to red.
- **UPDATED: `test-logo-theme-e2e.js`** — replaces the old "must NOT be
sage" sentinel (sage was a regression marker; it's now the brand
default) with a theme-reactivity probe that overrides `--logo-accent` /
`--logo-accent-hi` directly and asserts the wordmark fill changes.
Duotone, mobile-fit, and clip checks are unchanged.

## Verification

- Default load: sage CORE + teal SCOPE in navbar AND hero ✔ (asserted by
step 1 of the new e2e).
- Customizer override: wordmark follows `accent` / `accentHover` ✔
(asserted by step 2 of the new e2e + the theme-reactivity probe in
`test-logo-theme-e2e.js`).
- Preflight: all hard gates green (PII, branch scope, red commit,
CSS-var defined, CSS self-fallback, LIKE-on-JSON, sync migration); all
warnings green.

## Browser verified

E2E assertion added: `test-logo-default-sage-teal-e2e.js:73` (default
sage), `test-logo-default-sage-teal-e2e.js:124` (customizer override).
CI runs both via `deploy.yml:243`.

Browser verified: covered by Chromium e2e against
`http://localhost:13581` in CI; staging URL TBD on merge.

---------

Co-authored-by: openclaw-bot <bot@openclaw.local>
2026-05-07 08:29:02 -07:00
Kpa-clawbot dfacfd0f6e fix(logo): widen navbar SVG viewBox so CORE/SCOPE wordmark fits (#1141 followup) (#1156)
Fixes #1141 follow-up — the visible-on-staging SCOPE→SCOP clip that the
prior PRs (#1137, #1141) intended to address but didn't.

## What was actually broken (ground truth from staging)

Staging at `http://20.109.157.39:80/` renders the inline navbar SVG
correctly — duotone CORE/SCOPE fills inherit page CSS vars, mobile
mark-only swap fires at ≤400px, customizer logo override path works.
Those parts of #1137 + #1141 landed cleanly.

What did **NOT** land: the SVG `viewBox` was never widened to fit the
rendered Aldrich wordmark. At every desktop viewport the SCOPE `<text
text-anchor="start" x="773.8">` produces a bbox extending to user-space
x≈1112, but the navbar `viewBox="170 10 860 280"` ends at x=1030.
Result: SCOPE renders as **SCOP** on every desktop load. CORE also
slightly overflows the left edge (bbox.x=153.7 < viewBox.x=170).

The original brief premise (mushroom emoji still in `index.html` +
`<img>`-loaded SVG monotone fallback on staging) does not match current
state — `public/index.html:45` already has the inline SVG, staging
renders it, and computed fills are duotone (`rgb(74,158,255)` vs
`rgb(109,179,255)`). The visible bug is geometric clipping, not CSS-var
inheritance or a mushroom revert.

## Fix (one-liner SVG geometry change)

- `public/index.html` — navbar `svg.brand-logo`: `viewBox="170 10 860
280"` → `viewBox="150 10 970 280"`; intrinsic `width="111"` →
`width="125"` (preserves ~36px nav row height).
- `public/style.css` — `.brand-logo { width }` 111px → 125px (desktop),
tablet `@media (max-width:900px)` pin 99px → 112px to keep the new
aspect ratio so wordmark still doesn't clip on tablets.
- `public/customize-v2.js` — `_setBrandLogoUrl` `<img>` swap dimensions
updated to match (when an operator overrides `branding.logoUrl`).

The `≤400px` mobile mark-only swap is unchanged — at narrow widths the
wordmark still hides entirely and the dedicated `.brand-mark-only` SVG
(no `<text>`) renders.

## TDD (red → green)

| commit | role |
|---|---|
| `16b7a60` | **RED** — `test-logo-theme-e2e.js` assertion #7: every
`CORE`/`SCOPE` `<text>` bbox must fit inside the SVG `viewBox`. Master
fails: `[{text:CORE, bboxX:153.7, bboxRight:426.2, vbX:170},
{text:SCOPE, bboxX:773.8, bboxRight:1111.5, vbRight:1030}]` |
| `0db473b` | **GREEN** — widen viewBox + width to fit |

Test exercises real `getBBox()` measurement on a headless Chromium DOM
with the Aldrich webfont loaded — not a unit-test fill string check. The
earlier #1141 tests asserted computed `fill` colors (which were correct)
but never measured rendered geometry; that's the gap.

## Visual proof

**Before** (master HEAD against staging, viewport 1280):

`/tmp/staging-logo-before-1280.png` — SCOPE clearly clipped to "SCOP".

**After** (this branch against local server, viewport 1280):

`/tmp/local-after-1280-screen.png` — full CORE / SCOPE rendered.

**Mobile (after, 375px)**: `/tmp/local-after-mobile.png` — mark-only SVG
(no wordmark, no clip).

## Preflight

`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
— all hard gates clean (PII, branch-scope, red-commit-genuine,
css-vars-defined, css-self-fallback, like-on-json, sync-migration), all
warnings clean (img-svg-ratio, themed-img-svg, fixture-coverage).

E2E assertion added: `test-logo-theme-e2e.js:286-310`
Browser verified: `/tmp/local-after-1280-screen.png` (local server) +
`/tmp/staging-logo-before-1280.png` (staging baseline).

---------

Co-authored-by: corescope-bot <bot@corescope.local>
2026-05-06 23:39:10 -07:00
Kpa-clawbot 0d8bc7536c fix(logo): restore duotone fog/teal split + mobile mark-only swap (#1141)
Two related logo fixes bundled together (small scope each).

Cc @user-display-not-by-name.

## 1. Restore duotone (fog/teal) split — the original ask

The M2 (light-theme readability) fix-cycle on #1137 collapsed both
halves of the inline CoreScope wordmark to `var(--logo-text)` so they
would invert correctly on light themes. That restored readability but
erased the original side-split palette.

This change re-uses the existing `--logo-accent` / `--logo-accent-hi`
vars (already driving the left/right node arcs and dots) for the
wordmark too:

- `CORE` → `fill="var(--logo-accent)"` — matches left arcs + left node
dot
- `SCOPE` → `fill="var(--logo-accent-hi)"` — matches right arcs + right
node dot
- chirp polyline + `MESH ANALYZER` tagline → unchanged,
`var(--logo-muted)`

No hardcoded hex; theme customizer overrides via `--accent` /
`--accent-hover` keep working on both themes.

## 2. Fix mobile clipping (SCOPE → "SCOF" at ≤390px)

The full inline wordmark SVG has ~111px intrinsic content; the
`.brand-logo` mobile pin from #1137 (99px width) was squeezing it and
visibly clipping SCOPE.

**Approach:** swap the full wordmark SVG for a dedicated mark-only
inline SVG at ≤400px (option #1 from the design call). Keeps the duotone
arcs, dots, and chirp visible — drops the wordmark cleanly.

- `public/index.html`: CORE/SCOPE wrapped in `<g
class="brand-wordmark">` (clean grouping); new sibling `<svg
class="brand-mark-only">` with tight viewBox `425 15 250 230` covering
both nodes + dots only. Same `--logo-accent` / `--logo-accent-hi` vars →
duotone preserved on mobile.
- `public/style.css`: `.brand-mark-only` defaults `display:none`; new
`@media (max-width:400px)` rule hides `.brand-logo` and shows
`.brand-mark-only`.

## TDD

Three commits, red→green→red→green:

| commit | role |
|---|---|
| `d53d328` | RED — duotone assertions (#4, #5) added; master fails
(CORE === SCOPE) |
| `3e53031` | GREEN — split CORE/SCOPE fills |
| `e6b078f` | RED — mobile mark-only swap assertion (#6) at 360x640;
master fails (no `.brand-mark-only`) |
| `1a3b5db` | GREEN — add the mark-only SVG + media-query swap |

## Files changed

- `test-logo-theme-e2e.js` — assertions expanded from 3/3 to 6/6
- `public/index.html` — duotone fills + brand-wordmark grouping +
brand-mark-only sibling SVG
- `public/home.js` — duotone fills (hero)
- `public/style.css` — `.brand-mark-only` defaults + `@media
(max-width:400px)` swap rule

## Verification

CI Playwright run on commit `3e53031` (after the duotone fix, before the
mobile fix) confirmed assertions 1–5 pass:
- `navbar duotone preserved (dark: CORE=rgb(74,158,255)
SCOPE=rgb(109,179,255); light: CORE=rgb(74,158,255)
SCOPE=rgb(109,179,255))`
- `hero duotone preserved (dark: CORE=rgb(74,158,255)
SCOPE=rgb(109,179,255); light: CORE=rgb(74,158,255)
SCOPE=rgb(109,179,255))`

Final CI run on `1a3b5db` will additionally exercise the 6th (mobile
mark-only swap at 360×640).

---------

Co-authored-by: corescope-bot <bot@corescope.local>
2026-05-07 05:05:24 +00:00
Kpa-clawbot 364c5766fc feat(logo): wire new CoreScope SVG logo into navbar + home hero (#1137)
## Adds new logo and home hero

Replaces the navbar mushroom emoji + "CoreScope" text spans with the new
CoreScope SVG mark, and adds a hero SVG (with the MESH ANALYZER tagline)
above the home page H1.

### What changed
- `public/img/corescope-logo.svg` — navbar mark, no tagline (locked
"aggressive low-amp chirp" variant: facing-arcs + low-amp chirp
connector between the two nodes).
- `public/img/corescope-hero.svg` — home hero version, includes the MESH
ANALYZER tagline.
- `public/index.html` — replaces `<span class="brand-icon">🍄</span><span
class="brand-text">CoreScope</span>` with `<img class="brand-logo"
src="img/corescope-logo.svg?__BUST__" …>`. `.nav-brand` link still
routes to `#/`. `.live-dot` retained.
- `public/style.css` — adds `.brand-logo { height: 36px }` (32px on
tablet ≤900px). Existing 52px nav height unchanged.
- `public/home.js` / `public/home.css` — adds `<img
class="home-hero-logo">` above the hero `<h1>`, sized `max-width:
min(720px, 90vw)` and centered.

### TDD
Red→green is visible in the branch:
- `3159b82` — `test(logo): add failing E2E …` (red commit). Adds
`test-logo-rebrand-e2e.js` and wires it into the `e2e-test` job in
`deploy.yml` with `CHROMIUM_REQUIRE=1`. On this commit `index.html`
still has the emoji + text spans, `home.js` has no hero img, and the SVG
asset files do not exist — the test asserts on each so CI fails on
assertion.
- `19434e1` — `feat(logo): wire new CoreScope SVG logo …` (green
commit). Implements the fix.

### E2E asserts
1. `.nav-brand img` exists with `src` ending `corescope-logo.svg`
2. legacy `.brand-icon` / `.brand-text` are gone
3. `.live-dot` is present, visible, and to the right of the logo (no
overlap)
4. `.home-hero img.home-hero-logo` exists with `src` ending
`corescope-hero.svg`, positioned BEFORE the `<h1>`
5. both `/img/corescope-{logo,hero}.svg` return 200 with svg
content-type

### Customizer compatibility
- `customize.js` still does `querySelector('.brand-text')` /
`.brand-icon` for live branding updates. Both now return `null`;
existing `if (el)` guards make those branches silent no-ops. **No JS
errors, but the customizer's `branding.siteName` and `branding.logoUrl`
fields no longer rewrite the navbar brand** — the brand is now a fixed
SVG asset.
- **Theme accent does NOT recolor the SVG.** SVGs loaded via `<img src>`
are isolated documents and cannot inherit document CSS variables; the
SVG falls back to its embedded brand colors. This is appropriate for a
brand mark; if recoloring per theme is desired later, swap to inline SVG
(separate PR).

### Browser validation
Local Chromium not available in this env; the E2E test soft-skips
locally and hard-fails in CI (`CHROMIUM_REQUIRE=1`). Server-side checks
done locally:
- `curl http://localhost:13581/` → confirmed `<img class="brand-logo"
src="img/corescope-logo.svg?<bust>" …>` rendered, no
`.brand-icon`/`.brand-text` spans.
- `curl -I /img/corescope-logo.svg` and `/img/corescope-hero.svg` → both
200.

### Performance
No hot-path changes. Two new static SVG assets (~7.6KB each), served
directly by the Go static handler. Cache-busted via `?__BUST__`
(auto-replaced server-side).

---------

Co-authored-by: OpenClaw Bot <bot@openclaw.local>
Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
2026-05-06 19:17:46 -07:00