diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e3d15f..5150f3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ All notable changes to this project will be documented in this file. - **NomadNet file downloads (cancel)**: Fixed `AttributeError` when cancelling a download — `RequestReceipt` has no `.cancel()`; we now cancel the underlying `Resource` if present, or mark the receipt `FAILED` and remove it from the link queue. - **NomadNet browser (links)**: Relative `/page/` and `/file/` URLs from the Micron parser (which include backtick parameters) are now parsed correctly so they no longer show "Unsupported URL". - **NomadNet browser (hover)**: Links with `data-destination` now show the full URL including backtick parameters in the browser hover title. +- **Docker build**: `build-frontend` stage now installs `python3` so docs generation succeeds in `node:24-alpine`. +- **Docs manager**: Markdown tables in generated documentation render with proper borders and padding. ### Added @@ -47,6 +49,9 @@ All notable changes to this project will be documented in this file. - **NomadNet query tests**: Frontend and backend tests for `parseNomadnetworkUrl` with query strings and `downloadNomadNetFile` data payload handling. - **Android RNode protection**: On Android, `RNodeInterface`, `RNodeIPInterface`, and `RNodeMultiInterface` entries in the Reticulum config are automatically disabled before startup to prevent crashes from missing serial/BLE support in Chaquopy. - **Android external storage**: On Android, MeshChatX now defaults to `getExternalFilesDir()` (user-accessible via file managers) instead of private internal storage. +- **CI (Linux packages)**: AppImage, deb, and rpm release assets are now built and tested on every push to `dev` for both x64 and arm64. +- **Docker**: Added a hardened image variant (`-hardened` suffix) with non-root user, read-only rootfs, and restricted capabilities. +- **Map**: Drag-and-drop import of GeoJSON, KML, and KMZ files directly onto the map window, with localized drop hint overlays. ### Changed @@ -61,6 +66,7 @@ All notable changes to this project will be documented in this file. - **Sidebar order**: Reordered sidebar so **Telephone** appears directly below **Messages** for faster access. - **Telephone announce**: Disabled by default in `config_manager`. - **CONTRIBUTING.md**: Updated generative AI policy to emphasize local/offline models and reference the Reticulum Zen and License. +- **Dependencies**: Migrated Python dependency management from **Poetry** to **UV** (0.11.12) across all CI scripts, Dockerfiles, and dev tooling. `poetry.lock` replaced with `uv.lock`. ## [4.6.1] - 2026-05-04 diff --git a/Dockerfile b/Dockerfile index 5689195..c684069 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,17 +16,19 @@ ARG PYTHON_HASH=sha256:dd4d2bd5b53d9b25a51da13addf2be586beebd5387e289e798e4083d9 # ---- STAGE 1: Frontend Build ---- FROM --platform=linux/amd64 ${NODE_IMAGE}@${NODE_HASH} AS build-frontend WORKDIR /src -RUN apk add --no-cache git +RUN apk add --no-cache git python3 COPY package.json pnpm-lock.yaml vite.config.js ./ COPY patches ./patches COPY scripts/fetch-micron-wasm.mjs scripts/fetch-micron-wasm.mjs COPY scripts/micron-wasm-resolve-bundled.mjs scripts/micron-wasm-resolve-bundled.mjs COPY scripts/micron-parser-go-version.mjs scripts/micron-parser-go-version.mjs +COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py COPY meshchatx/src/frontend ./meshchatx/src/frontend RUN npm install -g pnpm@10.33.0 && \ pnpm config set verify-store-integrity true && \ pnpm install --frozen-lockfile && \ - pnpm run build-frontend + pnpm run build-frontend && \ + pnpm run build-docs # ---- STAGE 2: Python Builder ---- @@ -45,7 +47,8 @@ ENV PATH="/opt/venv/bin:$PATH" # Install essential runtime tools in the venv (cffi verify needs setuptools on Python 3.12+) RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" -COPY pyproject.toml uv.lock README.md ./ +COPY pyproject.toml uv.lock README.md CHANGELOG.md ./ +COPY logo ./logo COPY vendor ./vendor RUN uv sync --no-group dev --no-install-project && \ rm -rf /root/.cache/pip /root/.cache/uv diff --git a/Dockerfile.hardened b/Dockerfile.hardened index 95481bd..9078f46 100644 --- a/Dockerfile.hardened +++ b/Dockerfile.hardened @@ -13,17 +13,19 @@ ARG PYTHON_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev FROM --platform=linux/amd64 ${NODE_IMAGE} AS build-frontend USER root WORKDIR /src -RUN apk add --no-cache git +RUN apk add --no-cache git python3 COPY package.json pnpm-lock.yaml vite.config.js ./ COPY patches ./patches COPY scripts/fetch-micron-wasm.mjs scripts/fetch-micron-wasm.mjs COPY scripts/micron-wasm-resolve-bundled.mjs scripts/micron-wasm-resolve-bundled.mjs COPY scripts/micron-parser-go-version.mjs scripts/micron-parser-go-version.mjs +COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py COPY meshchatx/src/frontend ./meshchatx/src/frontend RUN npm install -g pnpm@10.33.0 && \ pnpm config set verify-store-integrity true && \ pnpm install --frozen-lockfile && \ - pnpm run build-frontend + pnpm run build-frontend && \ + pnpm run build-docs FROM ${PYTHON_BUILD_IMAGE} AS builder USER root @@ -37,7 +39,8 @@ ENV PATH="/opt/venv/bin:$PATH" RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" -COPY pyproject.toml uv.lock README.md ./ +COPY pyproject.toml uv.lock README.md CHANGELOG.md ./ +COPY logo ./logo COPY vendor ./vendor RUN uv sync --no-group dev --no-install-project && \ rm -rf /root/.cache/pip /root/.cache/uv diff --git a/meshchatx/src/frontend/components/docs/DocsPage.vue b/meshchatx/src/frontend/components/docs/DocsPage.vue index deb7be4..1ccb9e2 100644 --- a/meshchatx/src/frontend/components/docs/DocsPage.vue +++ b/meshchatx/src/frontend/components/docs/DocsPage.vue @@ -856,4 +856,41 @@ iframe { .dark :deep(.max-w-none) h4 { color: #f4f4f5; /* zinc-100 */ } + +/* Markdown table styling */ +:deep(.max-w-none) table { + width: 100%; + border-collapse: collapse; + margin: 1rem 0; + font-size: 0.875rem; +} + +:deep(.max-w-none) th, +:deep(.max-w-none) td { + border: 1px solid #d1d5db; + padding: 0.5rem 0.75rem; + text-align: left; +} + +:deep(.max-w-none) th { + background-color: #f3f4f6; + font-weight: 700; +} + +:deep(.max-w-none) tr:nth-child(even) { + background-color: #f9fafb; +} + +.dark :deep(.max-w-none) th, +.dark :deep(.max-w-none) td { + border-color: #3f3f46; +} + +.dark :deep(.max-w-none) th { + background-color: #27272a; +} + +.dark :deep(.max-w-none) tr:nth-child(even) { + background-color: #18181b; +} diff --git a/meshchatx/src/frontend/components/map/MapPage.vue b/meshchatx/src/frontend/components/map/MapPage.vue index b90b51e..5e8dd7b 100644 --- a/meshchatx/src/frontend/components/map/MapPage.vue +++ b/meshchatx/src/frontend/components/map/MapPage.vue @@ -149,7 +149,24 @@ /> -
+
+ + +
+ +

{{ $t("map.drop_geo_files") }}

+

GeoJSON, KML, or KMZ

+
{ + const name = f.name.toLowerCase(); + return ( + name.endsWith(".geojson") || + name.endsWith(".json") || + name.endsWith(".kml") || + name.endsWith(".kmz") + ); + }); + if (!geoFiles.length) { + ToastUtils.warning(this.$t("map.drop_no_geo_files")); + return; + } + + for (const file of geoFiles) { + try { + const name = file.name.toLowerCase(); + let features = []; + if (name.endsWith(".kmz")) { + const buf = await this.readFileArrayBuffer(file); + features = await readKmzToFeatures(buf, "EPSG:3857"); + } else if (name.endsWith(".kml")) { + const text = await this.readFileText(file); + features = readKmlToFeatures(text, "EPSG:3857"); + } else { + const text = await this.readFileText(file); + features = readGeoJsonToFeatures(text, "EPSG:3857"); + } + this.onVectorExchangeImport({ features, merge: true }); + } catch (e) { + console.error("Map drop import failed:", e); + ToastUtils.error(this.$t("map.vector_import_failed") + ` — ${file.name}`); + } + } + }, + readFileText(file) { + return new Promise((resolve, reject) => { + const r = new FileReader(); + r.onload = () => resolve(String(r.result || "")); + r.onerror = () => reject(new Error("read failed")); + r.readAsText(file); + }); + }, + readFileArrayBuffer(file) { + return new Promise((resolve, reject) => { + const r = new FileReader(); + r.onload = () => resolve(/** @type {ArrayBuffer} */ (r.result)); + r.onerror = () => reject(new Error("read failed")); + r.readAsArrayBuffer(file); + }); + }, + exportVectorGeoJson() { if (!this.drawSource || !this.hasVectorDrawFeatures) { return; diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json index 53c4df2..8e033eb 100644 --- a/meshchatx/src/frontend/locales/de.json +++ b/meshchatx/src/frontend/locales/de.json @@ -1131,7 +1131,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Kartendatei hier ablegen", + "drop_no_geo_files": "Keine GeoJSON-, KML- oder KMZ-Dateien erkannt." }, "interface": { "disable": "Deaktivieren", diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json index 609510d..9893c55 100644 --- a/meshchatx/src/frontend/locales/en.json +++ b/meshchatx/src/frontend/locales/en.json @@ -1079,7 +1079,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Drop map file here", + "drop_no_geo_files": "No GeoJSON, KML, or KMZ files detected." }, "interface": { "disable": "Disable", diff --git a/meshchatx/src/frontend/locales/es.json b/meshchatx/src/frontend/locales/es.json index 170c931..d36039c 100644 --- a/meshchatx/src/frontend/locales/es.json +++ b/meshchatx/src/frontend/locales/es.json @@ -1079,7 +1079,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Suelta el archivo del mapa aquí", + "drop_no_geo_files": "No se detectaron archivos GeoJSON, KML o KMZ." }, "interface": { "disable": "Inhabilitación", diff --git a/meshchatx/src/frontend/locales/fr.json b/meshchatx/src/frontend/locales/fr.json index 9cae253..701c125 100644 --- a/meshchatx/src/frontend/locales/fr.json +++ b/meshchatx/src/frontend/locales/fr.json @@ -1079,7 +1079,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Déposez le fichier carte ici", + "drop_no_geo_files": "Aucun fichier GeoJSON, KML ou KMZ détecté." }, "interface": { "disable": "Désactiver", diff --git a/meshchatx/src/frontend/locales/it.json b/meshchatx/src/frontend/locales/it.json index 50dc856..e483eaf 100644 --- a/meshchatx/src/frontend/locales/it.json +++ b/meshchatx/src/frontend/locales/it.json @@ -1131,7 +1131,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Rilascia il file della mappa qui", + "drop_no_geo_files": "Nessun file GeoJSON, KML o KMZ rilevato." }, "interface": { "disable": "Disabilita", diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json index 6ad1eac..931f055 100644 --- a/meshchatx/src/frontend/locales/nl.json +++ b/meshchatx/src/frontend/locales/nl.json @@ -1079,7 +1079,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Sleep kaartbestand hierheen", + "drop_no_geo_files": "Geen GeoJSON-, KML- of KMZ-bestanden gedetecteerd." }, "interface": { "disable": "Uitschakelen", diff --git a/meshchatx/src/frontend/locales/ru.json b/meshchatx/src/frontend/locales/ru.json index 0ccdab3..5f95c08 100644 --- a/meshchatx/src/frontend/locales/ru.json +++ b/meshchatx/src/frontend/locales/ru.json @@ -1131,7 +1131,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "Перетащите файл карты сюда", + "drop_no_geo_files": "Не обнаружено файлов GeoJSON, KML или KMZ." }, "interface": { "disable": "Выключить", diff --git a/meshchatx/src/frontend/locales/zh.json b/meshchatx/src/frontend/locales/zh.json index c9fa026..76b32b2 100644 --- a/meshchatx/src/frontend/locales/zh.json +++ b/meshchatx/src/frontend/locales/zh.json @@ -1079,7 +1079,9 @@ "vector_import_ok": "Imported {count} feature(s).", "vector_import_empty": "No features found in file.", "vector_import_failed": "Could not read vector file.", - "vector_export_ok": "Export started." + "vector_export_ok": "Export started.", + "drop_geo_files": "将地图文件拖放到此处", + "drop_no_geo_files": "未检测到 GeoJSON、KML 或 KMZ 文件。" }, "interface": { "disable": "禁用",