Alerts tab: fix hurricane/tropical storm bugs; rich alert cards matching the dashboard
Publish Container / publish (push) Successful in 2m44s
Publish Container / publish (push) Successful in 2m44s
Hurricane / tropical storm fixes - NHC intensity is in knots: show real mph (80 kt = 92 mph) instead of mislabelled 'mph' - Real Saffir-Simpson category from wind (was always cat 1); subtropical, post-tropical and potential-cyclone types no longer shown as 'depression'; each storm shows movement (direction + mph, previously a non-existent field), distance from you, update time, advisory link - Forecast track points coloured by their own forecast strength (not the current class) - Map click handlers registered once (popups stacked on every refresh); overlays wait for the map to settle instead of silently not drawing while tiles load; stale renders superseded - Subtropical/tropical advice added to alert guidance; escaped storm names Alert cards (active alerts only) - What it's for, area, window, time remaining, plain-language meaning, what to do, and a Full details expander (headline, text, instructions, severity/urgency/certainty, issuer) - No longer drops alerts beyond 6 per severity group; expired alerts are filtered out - Shared plain-language alert glossary (alert-info.js, 64 products incl. tropical and surge) now used by both the app and the dashboard history Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8252017417
commit
72a4a3fe38
8 files changed
+359
-196
No files matched your search
@@ -17,6 +17,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/hammerjs@2.0.8/hammer.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js"></script>
|
||||
<link rel="stylesheet" href="/dashboard-ui.css">
|
||||
<script src="/alert-info.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
@@ -1644,72 +1645,9 @@ const TAG_LABELS = {
|
||||
|
||||
let alertHistoryDays = 30;
|
||||
|
||||
// Plain-language meaning of common NWS products, so the history says what an alert is *for*
|
||||
const ALERT_GLOSSARY = {
|
||||
'heat advisory': 'Dangerously hot conditions expected. Limit time outdoors, drink plenty of water, and check on children, older adults and pets.',
|
||||
'excessive heat watch': 'Conditions are favorable for life-threatening heat in the next day or two. Plan ahead.',
|
||||
'excessive heat warning': 'Life-threatening heat is occurring or imminent. Avoid outdoor activity and stay cool.',
|
||||
'extreme heat watch': 'Conditions are favorable for life-threatening heat in the next day or two. Plan ahead.',
|
||||
'extreme heat warning': 'Life-threatening heat is occurring or imminent. Avoid outdoor activity and stay cool.',
|
||||
'severe thunderstorm watch': 'Storms capable of damaging wind or large hail are possible in the area. Stay alert and be ready to take shelter.',
|
||||
'severe thunderstorm warning': 'A severe storm with damaging wind and/or large hail is happening or imminent. Move indoors away from windows.',
|
||||
'tornado watch': 'Conditions are favorable for tornadoes. Stay alert and know where you would take shelter.',
|
||||
'tornado warning': 'A tornado is occurring or imminent. Take shelter immediately in the lowest, most interior room.',
|
||||
'special weather statement': 'A short notice about a significant but non-warning weather event, often a strong storm, gusty winds, dense fog or sudden changes.',
|
||||
'flood watch': 'Flooding is possible. Be prepared to move to higher ground and avoid low-lying areas.',
|
||||
'flood advisory': 'Minor flooding is occurring or expected. Avoid flooded roads and low spots.',
|
||||
'flood warning': 'Flooding is happening or imminent. Move to higher ground and never drive through flood water.',
|
||||
'flash flood watch': 'Flash flooding is possible. Be ready to move to higher ground quickly.',
|
||||
'flash flood warning': 'Flash flooding is occurring or imminent. Move to higher ground now.',
|
||||
'wind advisory': 'Sustained or gusty winds that can blow around unsecured objects and make driving difficult for high-profile vehicles.',
|
||||
'high wind watch': 'Damaging winds are possible. Secure outdoor objects.',
|
||||
'high wind warning': 'Damaging winds are occurring or imminent. Secure objects and avoid travel if possible.',
|
||||
'freeze watch': 'A freeze is possible. Protect sensitive plants, pets and exposed pipes.',
|
||||
'freeze warning': 'Temperatures at or below freezing are expected. Protect plants, pets and exposed pipes.',
|
||||
'frost advisory': 'Frost is expected and can damage sensitive plants.',
|
||||
'hard freeze warning': 'Severe freezing temperatures are expected. Protect plants, pets and pipes.',
|
||||
'winter storm watch': 'Heavy snow and/or ice is possible. Prepare for hazardous travel.',
|
||||
'winter storm warning': 'Heavy snow and/or ice is occurring or imminent. Travel may be dangerous or impossible.',
|
||||
'winter weather advisory': 'Snow, sleet or freezing rain will cause slippery roads and travel difficulty.',
|
||||
'ice storm warning': 'Significant ice accumulation is expected. Power outages and dangerous travel are likely.',
|
||||
'blizzard warning': 'Heavy snow with strong winds and very low visibility. Avoid travel.',
|
||||
'wind chill advisory': 'Dangerously cold wind chills are expected. Dress in layers and limit time outside.',
|
||||
'wind chill warning': 'Life-threatening wind chills are expected. Frostbite can occur quickly.',
|
||||
'dense fog advisory': 'Fog is reducing visibility to a quarter mile or less. Slow down and use low-beam headlights.',
|
||||
'dense smoke advisory': 'Smoke is reducing visibility and air quality.',
|
||||
'air quality alert': 'Air quality is unhealthy for some or all people. Limit prolonged outdoor exertion.',
|
||||
'red flag warning': 'Dry, windy and warm conditions create a high wildfire risk. Avoid open flames and outdoor burning.',
|
||||
'fire weather watch': 'Conditions may soon create a high wildfire risk.',
|
||||
'rip current statement': 'Dangerous rip currents at the beach. Stay out of the water or swim near a lifeguard.',
|
||||
'coastal flood advisory': 'Minor coastal flooding is expected.',
|
||||
'coastal flood warning': 'Significant coastal flooding is occurring or imminent.',
|
||||
'small craft advisory': 'Winds or seas hazardous to small boats.',
|
||||
'tropical storm watch': 'Tropical storm conditions are possible within about 48 hours.',
|
||||
'tropical storm warning': 'Tropical storm conditions are expected within about 36 hours.',
|
||||
'hurricane watch': 'Hurricane conditions are possible within about 48 hours.',
|
||||
'hurricane warning': 'Hurricane conditions are expected within about 36 hours.',
|
||||
'hazardous weather outlook': 'An outlook of possible hazardous weather in the next several days.',
|
||||
'severe weather statement': 'An update on an ongoing severe storm warning.',
|
||||
'flood statement': 'An update on an ongoing flood warning.',
|
||||
};
|
||||
function alertMeaning(event) {
|
||||
const e = String(event || '').toLowerCase().trim();
|
||||
if (ALERT_GLOSSARY[e]) return ALERT_GLOSSARY[e];
|
||||
if (e.endsWith('watch')) return 'A Watch: conditions are favorable for this hazard. Stay alert and be ready to act.';
|
||||
if (e.endsWith('warning')) return 'A Warning: this hazard is happening or imminent. Take action now.';
|
||||
if (e.endsWith('advisory')) return 'An Advisory: conditions may cause inconvenience or danger if you are not careful.';
|
||||
if (e.endsWith('statement')) return 'An informational statement about developing or ongoing weather.';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Plain-language helpers are shared with the weather app (alert-info.js)
|
||||
const { meaning: alertMeaning, firstSentence: ahFirstSentence, area: ahArea, when: ahWhen } = window.AlertInfo;
|
||||
const ahState = { items: [], open: new Set(), loading: new Set(), gone: new Set(), queue: [], running: false };
|
||||
const ahWhen = (iso) => iso ? new Date(iso).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '';
|
||||
const ahFirstSentence = (t) => {
|
||||
// NWS text is often structured "* WHAT...", "* WHERE...", "* WHEN..."; lead with the WHAT part
|
||||
const what = String(t || '').match(/\*\s*WHAT\.\.\.([\s\S]*?)(?:\n\s*\n|\*\s*[A-Z ]+\.\.\.|$)/);
|
||||
const c = (what ? what[1] : String(t || '')).replace(/\s+/g, ' ').trim(); const m = c.match(/^.*?[.!?](\s|$)/); const r = (m ? m[0] : c).trim(); return r.length > 220 ? r.slice(0, 217) + '…' : r;
|
||||
};
|
||||
const ahArea = (a) => String(a || '').replace(/;\s*/g, ', ');
|
||||
const SEV_ORDER = { Extreme: 'sev-extreme', Severe: 'sev-severe', Moderate: 'sev-moderate', Minor: 'sev-minor' };
|
||||
|
||||
function ahRow(it, i) {
|
||||
@@ -1721,7 +1659,10 @@ function ahRow(it, i) {
|
||||
let what, meta = [];
|
||||
if (isNws) {
|
||||
what = it.description ? ahFirstSentence(it.description) : alertMeaning(it.event) || it.headline || '';
|
||||
if (it.areaDesc) meta.push(`📍 ${esc(ahArea(it.areaDesc))}`);
|
||||
if (it.areaDesc) {
|
||||
const parts = String(it.areaDesc).split(';').map(x => x.trim()).filter(Boolean);
|
||||
meta.push(`📍 ${esc(parts.length > 4 ? parts.slice(0, 4).join(', ') + ' +' + (parts.length - 4) + ' more' : ahArea(it.areaDesc))}`);
|
||||
}
|
||||
else if (ahState.loading.has(it.alertId)) meta.push('📍 looking up area…');
|
||||
if (it.severity && it.severity !== 'Unknown') meta.push(`<span class="ah-sev ${SEV_ORDER[it.severity] || ''}">${esc(it.severity)}</span>`);
|
||||
if (it.effective || it.expires) meta.push(`🕑 ${esc(ahWhen(it.effective))}${it.expires ? ' → ' + esc(ahWhen(it.expires)) : ''}`);
|
||||
|
||||
Reference in new issue
Block a user