Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- **No raw SQL outside `src/db/repositories/` and `src/server/migrations/`.** ESLint-enforced via `no-restricted-syntax` in `eslint.config.mjs`.
- **All DatabaseService methods are async** (`Async` suffix). Tests mock with `mockResolvedValue`.
- **Never push directly to main. Always use a branch.**
- **App-owned interface icons use `UiIcon`.** Do not hardcode emoji or Unicode icon stand-ins in JSX or locale UI copy. Use `BrandIcon` for supported Simple Icons brand marks. User/content/protocol emoji require an issue-referenced exception when the distinction is not obvious.
- After bulk find-and-replace or sed, verify modified functions have correct `async`/`await` signatures. Route handlers and callbacks need `async` if `await` was added inside.

### Response envelope
Expand Down
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ describe('YourFeature', () => {

## 🤖 Automated Checks

### Interface icons

- Use the shared `UiIcon` component and a semantic registry name for app-owned interface icons. Do not hardcode emoji, checkmarks, arrows, stars, or similar Unicode glyphs in components or translated UI copy.
- Use `BrandIcon` for supported brands. Brand SVG data comes from Simple Icons and must record its source and version; do not substitute a lookalike emoji.
- User-authored content and protocol/domain data (for example messages, reactions, waypoint symbols, and script-selected emoji) remain data, not interface icons. Any new source-level exception needs an issue-referenced ESLint disable explaining why it is content rather than UI.
- `npm run lint:ci` blocks new hardcoded UI glyphs. Existing violations are ratcheted while they are migrated to `UiIcon`.

Our CI/CD pipeline runs automatically on all PRs:

### GitHub Actions Workflows
Expand Down Expand Up @@ -240,4 +247,4 @@ If you have questions, feel free to:
- Ask in pull request comments
- Refer to existing code for patterns

Happy coding! 🚀
Happy coding! 🚀
3 changes: 0 additions & 3 deletions eslint-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1290,9 +1290,6 @@
"src/utils/logger.ts": {
"@typescript-eslint/no-explicit-any": 4
},
"src/utils/loraFrequency.test.ts": {
"@typescript-eslint/no-unused-vars": 1
},
"src/utils/meshcoreHelpers.test.ts": {
"@typescript-eslint/no-unused-vars": 1
},
Expand Down
40 changes: 38 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import typescriptParser from '@typescript-eslint/parser';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
import { noHardcodedUiGlyph } from './scripts/eslint-rules/no-hardcoded-ui-glyph.mjs';

export default [
{
Expand Down Expand Up @@ -44,6 +45,11 @@ export default [
'@typescript-eslint': typescript,
'react-hooks': fixupPluginRules(reactHooks),
'react-refresh': reactRefresh,
'meshmonitor-ui': {
rules: {
'no-hardcoded-ui-glyph': noHardcodedUiGlyph,
},
},
},
rules: {
...js.configs.recommended.rules,
Expand Down Expand Up @@ -140,7 +146,13 @@ export default [
// semantics). The SQL-ban selectors are not needed here — components/pages never
// touch the DB directly.
files: ['src/components/**', 'src/pages/**'],
ignores: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
ignores: [
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.ts',
'**/*.spec.tsx',
'src/components/icons/**',
],
rules: {
'no-restricted-syntax': [
'error',
Expand All @@ -159,6 +171,30 @@ export default [
],
},
},
{
// #4215: app-owned interface icons must come from the typed UiIcon registry.
// These narrow exceptions contain user-selected or on-mesh protocol content.
files: ['src/components/**', 'src/pages/**'],
ignores: [
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.ts',
'**/*.spec.tsx',
'src/components/icons/**',
'src/components/EmojiPickerModal/**',
'src/components/WaypointEditorModal.tsx',
'src/components/map/layers/WaypointsLayer.tsx',
'src/components/AutoAcknowledgeSection.tsx',
'src/components/AutoWelcomeSection.tsx',
'src/components/MeshCore/MeshCoreAutoAckSection.tsx',
'src/components/meshtasticAutomationTokens.ts',
'src/components/automations/catalog.ts',
'src/components/automations/AutomationsPage.tsx',
],
rules: {
'meshmonitor-ui/no-hardcoded-ui-glyph': 'error',
},
},
{
// Type-aware: no un-awaited promises in production code.
// Scoped to src non-test TS/TSX only (test files have project:false and
Expand All @@ -181,4 +217,4 @@ export default [
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
];
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"start": "node dist/server/server.js",
"preview": "vite preview",
"lint": "eslint .",
"lint:ci": "node scripts/lint-ratchet.mjs",
"lint:ci": "node --test scripts/eslint-rules/no-hardcoded-ui-glyph.node-test.mjs && node scripts/check-ui-locale-glyphs.mjs && node scripts/lint-ratchet.mjs",
"lint:baseline": "node scripts/lint-ratchet.mjs --update",
"typecheck": "tsc --noEmit",
"typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
Expand Down
22 changes: 11 additions & 11 deletions public/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@
"banners.update_available": "Update verfügbar: Version {{version}} verfügbar.",
"banners.view_release_notes": "Release-Hinweise anzeigen",
"banners.upgrade_now": "Jetzt upgraden",
"purgeModal.title": "⚠️ Daten löschen für {{nodeName}}",
"purgeModal.title": "Daten löschen für {{nodeName}}",
"purgeModal.warning": "Diese Aktionen können nicht rückgängig gemacht werden. Alle Daten für diesen Knoten werden dauerhaft gelöscht.",
"purgeModal.purgeMessages": "🗑️ Alle Nachrichten löschen",
"purgeModal.purgeTraceroutes": "🗺️ Traceroutes löschen",
"purgeModal.purgeTelemetry": "📊 Telemetrie löschen",
"purgeModal.purgeMessages": "Alle Nachrichten löschen",
"purgeModal.purgeTraceroutes": "Traceroutes löschen",
"purgeModal.purgeTelemetry": "Telemetrie löschen",
"purgeModal.deleteNodeTitle": "Knoten vollständig löschen:",
"purgeModal.deleteNodeDescription": "Wählen Sie, wie der Knoten gelöscht werden soll - nur aus der lokalen Datenbank oder sowohl vom Gerät als auch aus der Datenbank.",
"purgeModal.deleteLocal": "Knoten löschen (Nur lokale Datenbank)",
"purgeModal.deleteDevice": "🗑️ Vom Gerät UND Datenbank löschen",
"purgeModal.deleteLocal": "Knoten löschen (Nur lokale Datenbank)",
"purgeModal.deleteDevice": "Vom Gerät UND Datenbank löschen",
"header.connectedTo": "Verbunden mit: {{address}}",
"header.clickForStatus": "Klicken für Systemstatus",
"header.status.connected": "verbunden",
Expand Down Expand Up @@ -240,13 +240,13 @@
"channels.channel_name": "Kanalname:",
"channels.channel_number": "Kanalnummer:",
"channels.encryption": "Verschlüsselung:",
"channels.status_secure": "🔒 Verschlüsselt (Sicher)",
"channels.status_default_key": "🔐 Standardschlüssel (Nicht sicher)",
"channels.status_unencrypted": "🔓 Unverschlüsselt",
"channels.status_secure": "Verschlüsselt (Sicher)",
"channels.status_default_key": "Standardschlüssel (Nicht sicher)",
"channels.status_unencrypted": "Unverschlüsselt",
"channels.hide": "Ausblenden",
"channels.show": "Anzeigen",
"channels.enabled": "Aktiviert",
"channels.disabled": "Deaktiviert",
"channels.enabled": "Aktiviert",
"channels.disabled": "Deaktiviert",
"channels.purge_all_messages": "Alle Nachrichten löschen",
"channels.cannot_undo": "Diese Aktion kann nicht rückgängig gemacht werden",
"channels.location_sharing": "",
Expand Down
Loading
Loading