mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
* feat(region-routing): P1 tagging + region_routes primitive + read/write API + preview launcher - config.py: add Coverage.region_tagging (bool=False); add RegionRouteMatrix dataclass (enabled, cells) above NotificationsConfig; add region_routes field to NotificationsConfig; add explicit hydration branch for region_routes in _dict_to_dataclass mirroring destinations pattern. - coverage_area.py: add MonitoringArea.name (str|None=None, frozen); update areas_from_config to preserve name; refactor inline geom extraction from classify_event_areas into shared _event_geom_json helper; add matching_area_names(geom_json, areas)->list[str] (additive, all named matches, config-order, deduped; gate unchanged); add event_region_names convenience wrapper. - coverage_filter.py: add region_tagging ctor kwarg; stamp event.region/ regions before the gate when region_tagging=True and areas non-empty and not event.regions (never clobbers satpass preset). - pipeline/__init__.py: wire region_tagging into CoverageFilter construction. - notification_routes.py: add GET /notifications/regions (named coverage area names, config-order, deduped); GET /notifications/region-routing (matrix as JSON); POST /notifications/region-routing (explicit RMW — only region_routes changes, toggles/rules/destinations survive). - scripts/preview_dashboard.py: mesh-free launcher — dashboard API only, no mesh connector, no broadcast loop; vite runs separately. All 87 coverage tests pass; 300 total pass; 6 pre-existing failures unchanged (adapter config count mismatch + MeshCore EventType.NEW_CONTACT). * feat(region-routing): manual region x family matrix editor page Adds RegionRoutingMatrix.tsx — a plain editor over the region_routes config primitive. Rows = families (via useFamilies()), cols = regions (from GET /api/notifications/regions). Each cell exposes MT channel (ChannelPicker single + includeDisabled), MC channel name (text input), min_severity select (routine/priority/critical/immediate), and an enabled checkbox. Only cells where MT or MC is set are included in the sparse POST payload. Master enable toggle maps to top-level enabled. MT budget guard warns when more than 7 distinct MT indices are in use. Sticky family column; horizontal scroll for wide region sets. Registers route /region-routing in App.tsx and adds "Region Routing" nav entry (Map icon) under the Meshtastic section in Layout.tsx, immediately after Routing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(region-routing): regions endpoint reads saved (disk) coverage so routing columns are dynamic without a bot restart; preview reloads config after writes * feat(routing): unify MT/MC routing into per-family cards; region routing as an in-card expand; remove rules/destinations UI + standalone page * refactor(routing): move Meshtastic Routing from /notifications to /meshtastic/routing (mirror /meshcore/routing); redirect legacy path * feat(region-routing): dispatcher honors region_routes matrix (authoritative-on-match, per-region cooldown, per-channel dedup); non-matrix path unchanged Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(region-routing): matrix dedup key must match boot-restore 2-tuple form (prevents restart re-broadcast flood); regression test --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.4 KiB
JavaScript
110 lines
3.4 KiB
JavaScript
/*! https://mths.be/cssesc v3.0.0 by @mathias */
|
||
'use strict';
|
||
|
||
var object = {};
|
||
var hasOwnProperty = object.hasOwnProperty;
|
||
var merge = function merge(options, defaults) {
|
||
if (!options) {
|
||
return defaults;
|
||
}
|
||
var result = {};
|
||
for (var key in defaults) {
|
||
// `if (defaults.hasOwnProperty(key) { … }` is not needed here, since
|
||
// only recognized option names are used.
|
||
result[key] = hasOwnProperty.call(options, key) ? options[key] : defaults[key];
|
||
}
|
||
return result;
|
||
};
|
||
|
||
var regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/;
|
||
var regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/;
|
||
var regexAlwaysEscape = /['"\\]/;
|
||
var regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g;
|
||
|
||
// https://mathiasbynens.be/notes/css-escapes#css
|
||
var cssesc = function cssesc(string, options) {
|
||
options = merge(options, cssesc.options);
|
||
if (options.quotes != 'single' && options.quotes != 'double') {
|
||
options.quotes = 'single';
|
||
}
|
||
var quote = options.quotes == 'double' ? '"' : '\'';
|
||
var isIdentifier = options.isIdentifier;
|
||
|
||
var firstChar = string.charAt(0);
|
||
var output = '';
|
||
var counter = 0;
|
||
var length = string.length;
|
||
while (counter < length) {
|
||
var character = string.charAt(counter++);
|
||
var codePoint = character.charCodeAt();
|
||
var value = void 0;
|
||
// If it’s not a printable ASCII character…
|
||
if (codePoint < 0x20 || codePoint > 0x7E) {
|
||
if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) {
|
||
// It’s a high surrogate, and there is a next character.
|
||
var extra = string.charCodeAt(counter++);
|
||
if ((extra & 0xFC00) == 0xDC00) {
|
||
// next character is low surrogate
|
||
codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||
} else {
|
||
// It’s an unmatched surrogate; only append this code unit, in case
|
||
// the next code unit is the high surrogate of a surrogate pair.
|
||
counter--;
|
||
}
|
||
}
|
||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||
} else {
|
||
if (options.escapeEverything) {
|
||
if (regexAnySingleEscape.test(character)) {
|
||
value = '\\' + character;
|
||
} else {
|
||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||
}
|
||
} else if (/[\t\n\f\r\x0B]/.test(character)) {
|
||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||
} else if (character == '\\' || !isIdentifier && (character == '"' && quote == character || character == '\'' && quote == character) || isIdentifier && regexSingleEscape.test(character)) {
|
||
value = '\\' + character;
|
||
} else {
|
||
value = character;
|
||
}
|
||
}
|
||
output += value;
|
||
}
|
||
|
||
if (isIdentifier) {
|
||
if (/^-[-\d]/.test(output)) {
|
||
output = '\\-' + output.slice(1);
|
||
} else if (/\d/.test(firstChar)) {
|
||
output = '\\3' + firstChar + ' ' + output.slice(1);
|
||
}
|
||
}
|
||
|
||
// Remove spaces after `\HEX` escapes that are not followed by a hex digit,
|
||
// since they’re redundant. Note that this is only possible if the escape
|
||
// sequence isn’t preceded by an odd number of backslashes.
|
||
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) {
|
||
if ($1 && $1.length % 2) {
|
||
// It’s not safe to remove the space, so don’t.
|
||
return $0;
|
||
}
|
||
// Strip the space.
|
||
return ($1 || '') + $2;
|
||
});
|
||
|
||
if (!isIdentifier && options.wrap) {
|
||
return quote + output + quote;
|
||
}
|
||
return output;
|
||
};
|
||
|
||
// Expose default options (so they can be overridden globally).
|
||
cssesc.options = {
|
||
'escapeEverything': false,
|
||
'isIdentifier': false,
|
||
'quotes': 'single',
|
||
'wrap': false
|
||
};
|
||
|
||
cssesc.version = '3.0.0';
|
||
|
||
module.exports = cssesc;
|