767 Commits

Author SHA1 Message Date
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
Louis King 4d680b1de7 test(spam): cover scheduler, config cache, and scoring edge cases
Raise diff/patch coverage above the Codecov gate (was 79.8%, target 80.29%)
by exercising the previously-untested spam paths:

- subscriber: TestSpamRescoreScheduler covers the disabled early-return, the
  enabled thread spawning + one sweep + clean stop, and the swallowed-error
  branch of the background re-scoring loop.
- handler: a contact-message scoring test covers the contact log branch.
- spam: get/reset_spam_config caching, the zero-weight combine path, the
  default-`now` path, and the null-sender reset in rescore_recent.

Patch coverage for the change is now ~97%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:25:06 +01:00
Louis King c48db03afb feat(spam): score messages at ingest and hide likely spam
Add an optional, off-by-default spam-detection feature that scores each
message's spam likelihood at ingest, stores the score on the row, and lets
the display layer hide likely-spam by default behind a "show potential spam"
toggle. Nothing is ever dropped at ingest, so the threshold can be retuned
without reprocessing.

Scoring (collector/spam.py): windowed COUNT(*) over new
(path_prefix, received_at) and (sender_normalized, received_at) indexes —
joint path+sender signal plus a sender-name signal (trailing-digit suffix
stripped so bob1/bob2 collapse to bob). When the path is short/zero-hop or
absent, the name signal stands alone at full weight so local spam is still
flaggable. A background sweep re-scores recent rows with hindsight to catch
the leading edge of bursts. The collector logs each score (WARNING at/above
the threshold).

Display: the messages API gains include_spam and a master-switch-aware
hide-filter; the SPA shows the toggle + a badge only when the feature is on.

Config: FEATURE_SPAM_DETECTION is the single operator switch, bridged in
Compose to the backend SPAM_DETECTION_ENABLED for collector + api (mirrors
the FEATURE_PACKETS / RAW_PACKET_CAPTURE_ENABLED pattern). Both default off.

Works on SQLite and Postgres: DB-agnostic queries, an Alembic batch migration
for the three new columns + two indexes, and backend-aware collector test
fixtures (lifted db_backend/db_url into the shared conftest).

Also: move the meshcore-hub image pull_policy out of the base compose file.
It lived in docker-compose.yml as pull_policy: daily and made `make up` pull
the published image over a freshly built local one. Base is now policy-neutral
(default missing); dev sets pull_policy: build on the hub services so it only
ever uses local builds. Prod refreshes images via a manual `docker compose
... pull`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:11:39 +01:00
JingleManSweep 0e8950d4d1 Merge pull request #272 from ipnet-mesh/renovate/actions-checkout-7.x
chore(deps): update actions/checkout action to v7
2026-06-20 13:18:58 +01:00
renovate[bot] 0b534781da chore(deps): update actions/checkout action to v7 2026-06-18 21:40:43 +00:00
JingleManSweep 6bf3c1c62b Merge pull request #271 from ipnet-mesh/docs/reorganize-configuration-and-deployment
docs: centralise env vars and split deployment/observer/maintenance docs
v0.14.0
2026-06-18 12:36:04 +01:00
Louis King 973bf23fe8 docs: centralise env vars and split deployment/observer/maintenance docs
Move scattered configuration tables and operational sections out of the
README into dedicated reference documents:

- docs/configuration.md: single source of truth for all environment
  variables, grouped into 12 sections (Common, Database, Caching,
  Collector, Webhooks, Auth, Data Retention, API, Web Dashboard,
  Feature Flags, Traefik, Prometheus & Alertmanager)
- docs/deployment.md: production setup, reverse proxy, multi-instance,
  API scaling, Redis caching
- docs/observer.md: remote observers plus PACKETCAPTURE_* and
  SERIAL_PORT reference
- docs/maintenance.md: backup and restore

README is reduced from 712 to 385 lines; the ARM32/Raspberry Pi note
is dropped. database.md, auth.md, webhooks.md, and content.md have
their env-var tables removed and link back to configuration.md. Stale
cross-references in database.md, upgrading.md, and .env.example are
updated to point at the new locations.
2026-06-18 12:20:49 +01:00
JingleManSweep a96bb548f6 Merge pull request #269 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.8
2026-06-18 11:00:21 +01:00
renovate[bot] 8cd70f87af chore(deps): update anomalyco/opencode action to v1.17.8 2026-06-18 03:02:00 +00:00
JingleManSweep 4e25b2fbf0 Merge pull request #267 from ipnet-mesh/docs/tidy-database-docs-and-sqlite-deprecation
docs: tidy database docs and deprecate SQLite
2026-06-17 22:00:40 +01:00