776 Commits

Author SHA1 Message Date
JingleManSweep 5e9182f0b4 Merge pull request #293 from ipnet-mesh/feat/path-hash-bytes-filter
feat: persist path_hash_bytes column and add packet list filter
v0.15.6
2026-07-04 00:04:50 +01:00
Louis King c029eae524 feat: persist path_hash_bytes column and add packet list filter
Persist the path-hash byte width (1/2/3) as a nullable Integer column on
RawPacket, computed at ingest by the collector and backfilled for historical
rows via a self-contained Python migration. Replace the per-request Python
decode loop in the grouped-list route with a SQL MAX() aggregate + HAVING
filter, and add a discrete <select> filter (Any/1B/2B/3B) to the /packets
SPA page.

- Model: add path_hash_bytes column to RawPacket (nullable Integer)
- Migration: batch_alter_table add_column + keyset-paginated Python backfill
  reading decoded via Core select() on sa.JSON column (portable across
  SQLite and Postgres)
- Collector: compute path_hash_bytes at ingest via two-tier path-hash
  extraction (decoded.path -> payload.decoded.pathHashes)
- API: add func.max() aggregate to group query, HAVING filter on
  ?path_hash_bytes=1|2|3 param; delete Phase 3 decode loop and dead helper
- Frontend: add path-width select filter wired through query/apiParams/
  pagination/headerParams; add i18n keys (en/nl)
- Tests: 1186 passed, 22 skipped; collector + API + model coverage
2026-07-04 00:02:18 +01:00
JingleManSweep 1615c71e18 Merge pull request #292 from ipnet-mesh/feat/json-tree-path-flow
feat(web): expandable JSON tree, packet path flow, chart polish
v0.15.5
2026-07-03 22:38:39 +01:00
Louis King f845830848 feat(web): expandable JSON tree, packet path flow, chart polish
- Add reusable lit-html JSON tree component (json-tree.js) with
  expand/collapse-all toolbar and type-coloured primitives; replace
  flat <pre> decoded blocks on packet detail and packet group detail
- Render packet path as a complete flow with static sender (green dot)
  and observer (satellite dish) terminators around hop badges
- Fill homepage chart areas and move legend to top-right to match
  dashboard charts
- Add iconChevronRight helper and expand_all/collapse_all strings (en, nl)
2026-07-03 22:35:43 +01:00
JingleManSweep 0073723269 Merge pull request #291 from ipnet-mesh/feat/regional-numbers-filter-toggle
feat(web): locale-aware number formatting + filter panel redesign
v0.15.4
2026-07-03 16:10:18 +01:00
Louis King 41f7fefcda feat(web): convert auto-refresh button to toggle slider for consistency
Replace the pause/play icon button with a DaisyUI toggle toggle-sm
toggle-primary slider, matching the filter toggle's visual style.
The label shows iconRefresh + interval (e.g. '30s'); checked = actively
refreshing, unchecked = paused.
2026-07-03 16:07:27 +01:00
Louis King 2c25a9cd17 feat(web): add locale-aware number formatting and redesign filter panel
Replace raw integer counts with Intl.NumberFormat()-grouped numbers
across all SPA pages — stat cards, dashboard stats, list-page total
badges, inline reception/observer counts, chart axis ticks and
tooltips, map counts, and packet-group-detail fields. Formatting uses
the visitor's browser locale (no explicit locale argument), decoupled
from the admin's datetime_locale.

Redesign the filter panel on all five filter-bearing pages (nodes,
packets, advertisements, messages, map): replace the heavy DaisyUI
collapse card with a compact right-aligned toggle slider plus bare
filter fields rendered below the control row. Filter open-state
survives auto-refresh and navigation via DOM-read of #filter-toggle.

Also fixes a pre-existing bug where pubkey_prefix was missing from
nodes.js hasActiveFilters, causing the filter to not default open
when only the public-key-prefix field was filled.
2026-07-03 16:02:45 +01:00
JingleManSweep ee4e8fa768 Merge pull request #290 from ipnet-mesh/feat/dashboard-recent-adverts
feat(dashboard): enrich recent adverts card and add packets widget
v0.15.3
2026-07-03 14:34:24 +01:00
Louis King 215690f4bf feat(dashboard): enrich recent adverts card and add packets widget
Recent adverts card now shows route-type badges, an observer column
with three-way fallback (observers -> observed_by -> dash), and renders
all 10 rows instead of 5. The Type column hides on mobile to prevent
overflow, and the misleading cursor-help on observer badges is removed.

Also adds a Packets chart card to the dashboard and a packets stat to
the homepage, backed by the new dashboard packet-activity endpoint.

Promotes routeTypeBadge to a shared export in components.js (previously
local to advertisements.js), and enriches RecentAdvertisement with
route_type, observers, and observed_by fields.

Backend resolves observers via fetch_observers_for_events and
observer_node_id -> public_key in batched queries.
2026-07-03 14:31:44 +01:00
JingleManSweep 3c61a139a0 Merge pull request #289 from ipnet-mesh/fix/mobile-nav-clickthrough
fix(web): stop closed mobile nav from intercepting page clicks
v0.15.2
2026-07-03 00:16:33 +01:00
Louis King cb677b3545 fix(web): stop closed mobile nav from intercepting page clicks
The two-column mobile nav set display:block on #mobile-nav (an ID
selector) to override .menu's display:flex so column-count worked.
That ID specificity also beat DaisyUI's closed-state display:none
rule, so the menu stayed display:block while 'closed' (only opacity
dropped to 0). The invisible, two-column-wide overlay captured clicks
over underlying page content and fired SPA navigation.

Scope display:block (and the column layout) to the dropdown's open
state (.dropdown-open / :focus-within) so DaisyUI's display:none can
hide the menu and its links when closed.
2026-07-03 00:14:28 +01:00
JingleManSweep 74acc3bec8 Merge pull request #288 from ipnet-mesh/feat/more-ui-tweaks
feat(web): panel accent redesign, mobile nav two-column, light-mode polish
v0.15.1
2026-07-02 23:26:26 +01:00
Louis King 479c263f17 feat(web): redesign panel accent system, mobile nav two-column, and light-mode polish
- Replace panel-glow radial gradient with panel-accent flat tint + 5px
  colored left border for section identity
- Add theme-aware --panel-tint-strength/--panel-tint-bg variables: dark
  mode keeps 8% colored tint, light mode uses neutral grey fill (0%)
- Remove inline section color from stat-value numbers so they inherit
  base-content (white/black) while icons and borders keep section colors
- Fix hero layout: center welcome text vertically, drop spacer div,
  add flex-col to content wrapper
- Split mobile nav dropdown into two balanced columns (column-count: 2)
  with responsive width to reduce vertical height on phones
- Soften flash banner in light mode with direct amber oklch values to
  avoid hue drift from low-chroma color-mix
- Boost stat-title/stat-desc opacity from 60% to 80% in light mode for
  better contrast on grey panels
- Dashboard chart cards now use section colors instead of neutral,
  subtitles bumped from opacity-70 to opacity-80
2026-07-02 23:23:50 +01:00
JingleManSweep 9c8684183c Merge pull request #287 from ipnet-mesh/ui-cosmetic-polish
UI cosmetic polish and IBM Plex typography
2026-07-02 22:13:51 +01:00
JingleManSweep 921e8f0099 Merge branch 'main' into ui-cosmetic-polish 2026-07-02 22:11:49 +01:00
JingleManSweep eb5f7e77f5 Merge pull request #285 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.13
2026-07-02 22:06:26 +01:00
renovate[bot] 6ae656576b chore(deps): update anomalyco/opencode action to v1.17.13 2026-07-02 21:04:02 +00:00
JingleManSweep 86e41d3a08 Merge pull request #286 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.10
2026-07-02 22:03:29 +01:00
Louis King 510612d69b feat(web): adopt self-hosted IBM Plex Sans and IBM Plex Mono typography
Replace the OS system font stack with IBM Plex: the variable-weight
sans (100-700, one file per subset) for UI and headings, and Plex Mono
400 for the public keys, packet hashes, and hex that font-mono renders
across the app. Latin + latin-ext subsets only (shipped locales are
en/nl); no italics; mono is never rendered bold here.

Fonts are self-hosted from @fontsource packages via the existing
build.js vendor pipeline (no CDN), copied to static/vendor/fonts/ with
a hard build failure on wrong filenames. Wiring:

- input.css: @theme --font-sans/--font-mono + @font-face rules with
  unicode-ranges taken verbatim from the package CSS; Tailwind v4
  derives the document default from --font-sans, re-fonting daisyUI
  components with no other changes.
- spa.html: preload the latin sans variable woff2 (crossorigin, URL
  identical to the @font-face src) to minimize FOUT.
- charts.js: Chart.defaults.font.family to match (Chart.js otherwise
  uses Helvetica/Arial).
- error.html: name-prepend only; the page stays dependency-free.
- middleware.py: long-term immutable cache for /static/vendor/fonts/
  (stable names referenced from CSS, so ?v= versioning can't apply),
  with a matching test.
- app.css: slight hero-title letter-spacing tightening for Plex at
  display sizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:58:58 +01:00
Louis King 91a3fcf237 feat(web): make custom clickable elements keyboard accessible
The channel cards, member node badges, and member URL text are
clickable divs/spans, so keyboard users could not reach or activate
them. Add role/tabindex, Enter/Space key handling, and a
focus-visible outline (invisible to mouse users). daisyUI btn/link/
input components already ship focus-visible outlines, so only these
custom elements needed it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:32:25 +01:00
Louis King 79565326e2 style(web): normalize heading, spacing, and radius scales across pages
- Page <h1>s: text-3xl font-bold everywhere (packet-detail,
  packet-group-detail, channels were text-2xl; channels icon bumped to
  match).
- Page-header rows: mb-6 on all pages (list pages used mb-4).
- Empty states: text-center py-8 opacity-70 (members used py-12,
  channels py-10 opacity-60).
- QR backings and adopted-node rows: rounded-box like other
  panel-level surfaces; QR padding unified at p-2.
- Home hero welcome text: max-w-[90%] sm:max-w-[70%] so narrow screens
  don't wrap into a skinny column (desktop unchanged).
- Document the styling conventions in the components.js header so
  future pages don't drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:31:40 +01:00
Louis King bb929dfe5f refactor(web): remove deprecated class names and hoist map marker colors
Zero-visual-delta cleanups:

- Drop input-bordered/select-bordered (removed in daisyUI v5; emitted
  no CSS) from all inputs and selects.
- Rename bare `shadow` to `shadow-sm` — in Tailwind v4 bare `shadow` is
  a deprecated alias with the identical value.
- Hoist the hardcoded map marker hex colors into CSS variables in the
  app.css palette block (same values; markers sit on map tiles and stay
  theme-independent by design).
- Convert the five text-base-content/* outliers to the repo's dominant
  opacity-* muted-text idiom (identical rendering on plain text).
- Remove a dead ternary in renderNodeDisplay and an unused iconLock
  import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:30:07 +01:00
Louis King e2c7a0f987 fix(web): repair unstyled channel modal labels and invisible prose link hover
The channels modal still used daisyUI v4's label-text class, which was
removed in v5, so its form labels rendered unstyled. Replace with the
Tailwind equivalent of what v4 produced (text-sm, muted).

.prose a:hover used --color-primary-content (the on-primary foreground),
making hovered markdown links nearly invisible against the page
background. Blend primary toward base-content instead so the hover
shade works in both themes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:28:06 +01:00
renovate[bot] 47d1dcec7a chore(deps): update dependency daisyui to v5.6.10 2026-07-02 13:01:43 +00:00
JingleManSweep 35037c64f2 Merge pull request #284 from ipnet-mesh/renovate/tailwindcss-monorepo
chore(deps): update tailwindcss monorepo to v4.3.2
2026-06-30 22:39:11 +01:00
JingleManSweep d90e6c1ff7 Merge branch 'main' into renovate/tailwindcss-monorepo 2026-06-30 22:36:57 +01:00
JingleManSweep 75264265f4 Merge pull request #283 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.6
2026-06-30 22:36:44 +01:00
renovate[bot] 4fa9b9b6ab chore(deps): update dependency daisyui to v5.6.6 2026-06-29 23:16:09 +00:00
renovate[bot] 775c875ef2 chore(deps): update tailwindcss monorepo to v4.3.2 2026-06-29 17:10:38 +00:00
JingleManSweep 369b3c6eb5 Merge pull request #282 from ipnet-mesh/fix/fastapi-0.137-route-introspection
fix(tests): introspect mounted paths via OpenAPI schema, unpin fastapi
2026-06-25 21:03:42 +01:00
Louis King b413904ace fix(tests): introspect mounted paths via OpenAPI schema, unpin fastapi
FastAPI 0.137.0 refactored include_router to keep included routers as
nested objects rather than flattening their routes into app.routes, so
test_app_factory's `{route.path for route in app.routes}` no longer found
the /metrics route (the endpoint still serves; only this introspection
broke). FastAPI now treats router.routes as an internal implementation
detail.

Switch the metrics route checks to the public OpenAPI schema
(app.openapi()["paths"]), which is stable across versions and resolves
router prefixes correctly, and drop the <0.137.0 pin that was blocking
the upgrade.

Verified: app factory tests pass on both 0.136.3 and 0.137.2; full
tests/test_api suite (462 tests) passes on 0.137.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:00:35 +01:00
JingleManSweep 01077b3783 Merge pull request #281 from ipnet-mesh/feat/observer-ingestion-filters
feat(collector): observer allow/deny ingestion filter
2026-06-25 20:52:47 +01:00
Louis King 5fe8ce9156 feat(collector): add observer allow/deny ingestion filter
Restrict which remote observers may ingest events, keyed on the observer's
public key (the <public_key> segment of its LetsMesh upload topic). Anyone
with broker access can publish as an observer via JWT auth, so operators can
now gate ingestion.

- New ObserverFilter (case-insensitive prefix matching, allowlist overrides
  denylist, accept-all when both empty)
- New OBSERVER_ALLOWLIST / OBSERVER_DENYLIST collector settings, wired through
  the CLI, run_collector, create_subscriber, and Subscriber
- Filter applied at the top of _handle_mqtt_message: blocked observers' packets
  are dropped before any decode, raw-packet capture, or DB write; zero added
  work on the default accept-all path
- Tests: ObserverFilter unit tests, subscriber drop/allow integration tests,
  config parsing tests
- Docs: configuration.md, observer.md, upgrading.md (v0.16.0), .env.example,
  docker-compose.yml

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:25:29 +01:00
JingleManSweep 133e064a17 Merge pull request #279 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.11
2026-06-25 19:03:52 +01:00
renovate[bot] 9a8c21d3cf chore(deps): update anomalyco/opencode action to v1.17.11 2026-06-25 13:55:20 +00:00
JingleManSweep d86a00ea87 Merge pull request #278 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.10
v0.15.0
2026-06-24 22:49:34 +01:00
renovate[bot] 39900b15db chore(deps): update anomalyco/opencode action to v1.17.10 2026-06-24 21:44:53 +00:00
JingleManSweep 9df7bf2f92 Merge pull request #277 from ipnet-mesh/renovate/actions-cache-6.x
chore(deps): update actions/cache action to v6
2026-06-24 09:10:39 +01:00
renovate[bot] 316293f6a1 chore(deps): update actions/cache action to v6 2026-06-23 19:30:54 +00:00
JingleManSweep d1d621c35b Merge pull request #276 from ipnet-mesh/fix/message-row-height
fix(web): stop pre-wrap inflating message table row height
2026-06-23 09:55:17 +01:00
JingleManSweep c9d5b34587 Merge branch 'main' into fix/message-row-height 2026-06-23 09:50:53 +01:00
JingleManSweep ba185f0896 Merge pull request #274 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.9
2026-06-23 09:50:39 +01:00
Louis King c955ea7a64 fix(web): stop pre-wrap inflating message table row height
The spam-detection change wrapped the message table cell content in a
multi-line div/span so the spam badge could sit alongside the text, but left
white-space: pre-wrap on the td. The template literal's own indentation
(newlines + spaces around the div) then rendered as literal whitespace inside
the pre-wrap cell, padding out every row regardless of spam state.

Move pre-wrap onto the span that holds the message text so multi-line bodies
still wrap while the cell's layout whitespace collapses normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:49:54 +01:00
renovate[bot] 643e9fa423 chore(deps): update anomalyco/opencode action to v1.17.9 2026-06-23 08:08:04 +00:00
JingleManSweep 45a76a9ad3 Merge pull request #275 from ipnet-mesh/feat/spam-detection
feat(spam): score messages at ingest and hide likely spam
2026-06-23 09:07:26 +01:00
Louis King 461dbc5008 feat(spam): ship spam detection enabled by default
Make FEATURE_SPAM_DETECTION on by default with opt-out, mirroring
FEATURE_PACKETS: flip the web feature flag's Python default to true and the
Compose substitutions (collector/api/web) to :-true, so the shipped stack
scores and hides likely-spam without configuration. Opt out with
FEATURE_SPAM_DETECTION=false.

Update .env.example, docs/configuration.md and the v0.15 upgrade notes to
describe the feature as enabled-by-default with opt-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:00:16 +01:00
Louis King db952d632a fix(spam): align badge threshold with API hide-filter
The Messages page badged a row as spam at a hardcoded score >= 0.6 while the
API hides rows at SPAM_SCORE_THRESHOLD (now 0.65), so messages scored in
[0.6, 0.65) appeared flagged but were never hidden by the "show potential
spam" toggle.

Expose spam_score_threshold in the SPA config and use it for the badge so a
row is badged exactly when the API would hide it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:51:20 +01:00
Louis King caaecfb3c2 feat(spam): retune default scoring config and add v0.15 upgrade notes
Adjust the default spam-scoring knobs across Python settings, SpamConfig,
docker-compose, .env.example and docs to reduce false positives on chatty
legitimate users:

  SPAM_MIN_PATH_HOPS   5  -> 3
  SPAM_PATH_THRESHOLD  5  -> 6
  SPAM_NAME_THRESHOLD  5  -> 10
  SPAM_WEIGHT_PATH     0.7 -> 0.75
  SPAM_WEIGHT_NAME     0.3 -> 0.25
  SPAM_SCORE_THRESHOLD 0.6 -> 0.65

Also document the spam-detection feature and the pull_policy change in a
new v0.15.0 section of docs/upgrading.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:40:24 +01:00
JingleManSweep fa9e4c811a Merge branch 'main' into feat/spam-detection 2026-06-23 00:42:43 +01:00
Louis King 691a5f45c8 test(spam): fix mypy union-attr in sweep-error test
Guard the optional `_spam_rescore_thread` with an `is not None` assert before
calling `.is_alive()`, matching the sibling test. CI runs `pre-commit run
--all-files`, so mypy checks the test files too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:28:55 +01:00