Cabin names, unsaved-change highlight, collapsible matches

- Cabins are Economy / Premium / Business everywhere; the config stores
  economy/premium/business and still accepts AG/AP/AB and Go/Plus
- Config fields that differ from the saved config get a faded yellow
  background, new rows are highlighted in full, removed rows are counted;
  reverting a field clears its highlight and Save disables when clean
- Current matches collapse per watch to a summary row: flight counts,
  bookable seats per cabin with lost seats in red, latest "since";
  open/closed state is remembered

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Måns SivertssonandClaude Opus 5.5 committed 2026-10-13 08:37:40 +02:00
1 parent f1acd53a9c
commit cb3528d8b0
3 files changed
+136 -55

No files matched your search

+11 -2
View File
@@ -50,7 +50,7 @@ test('new seats notify once, unchanged calendar uses cache', async () => {
const { w, calls, sent } = setup([watch()], baseResponses());
assert.equal(await w.runCycle(), 2);
assert.equal(sent.length, 1);
assert.match(sent[0].body, /SAS Plus ×1/);
assert.match(sent[0].body, /Premium ×1/);
assert.match(sent[0].body, /OUT ARN→HND Wed 09 Dec 06:00→Thu 10 Dec 07:55, 1 stop/);
assert.deepEqual(calls, ['cal ARN HND', 'routes ARN HND 2026-12-09']); // 12-08 has only AG -> never fetched
@@ -167,7 +167,7 @@ test('seat drops are tracked against the peak, gone matches stay listed in red',
const e = state.matches.tokyo['outbound|SK1415-20261209-ARN-CPH|AP'];
assert.deepEqual([e.status, e.seats, e.peak], ['active', 8, 10]);
assert.equal(state.events[0].kind, 'down');
assert.match(state.events[0].line, /SAS Plus ×10 → ×8/);
assert.match(state.events[0].line, /Premium ×10 → ×8/);
assert.equal(sent.length, 1); // a drop is shown, not alerted
r['cal ARN HND'] = calendarPayload([], []);
@@ -209,3 +209,12 @@ test('a leg whose return disappeared is marked unpaired, not gone', async () =>
const st = Object.fromEntries(Object.entries(state.matches.tokyo).map(([k, e]) => [k.split('|')[1], e.status]));
assert.deepEqual(st, { 'SK1415-20261209-ARN-CPH': 'unpaired', 'SK1417-20261209-ARN-CPH': 'unpaired', 'SK984-20261220-HND-CPH': 'gone' });
});
test('cabins are configured by name; old codes and SAS names still work', () => {
const cabinsOf = (cabins) => core.validateConfig({ watches: [watch({ cabins })] }).watches[0].cabins;
assert.deepEqual(cabinsOf(['business', 'Economy']), ['AG', 'AB']);
assert.deepEqual(cabinsOf(['AP', 'go', 'Plus']), ['AG', 'AP']);
assert.deepEqual(cabinsOf(undefined), ['AG', 'AP', 'AB']);
assert.throws(() => cabinsOf(['first']), /unknown cabin "first", use economy, premium or business/);
assert.deepEqual(core.CABINS, { AG: 'Economy', AP: 'Premium', AB: 'Business' });
});