- the timeline's Δ glyph became a **Details** button, and it now appears on Update rows too (not just card-side changes); EN "Changed (card)" -> "Changed on card", RU "Изменено (картой)" -> "Изменено картой" - the details modal shows the affected unit — the whole file, or the single record that changed — **before and after** the change as hex views (16 bytes per row, ASCII column, never-observed bytes as `--`), with the bytes that changed and the bytes the operation *first observed* highlighted in two different colours, plus the changed/new byte counts and the APDU reference; the old before/after byte table is gone - `GET /api/sessions/<id>/files/image?key=…&change=…` returns those two views (segments with offsets) plus the change's metadata; the images are reconstructed from the current image by undoing the later changes (`FileTracker.change_images`), so nothing is duplicated per event — every value change is an event, hence the values are exact (a byte only first observed later can appear as known in both views) - tracker: change events carry `observed_ranges` (bytes that were unknown before the operation) next to `ranges`; an update that only observes new bytes now records an event (so it can be inspected); record-file ranges are in-record byte offsets (the record number stays in its own field), which the previous "record number as position" could not express for a hex highlight - tests: change-images reconstruction (before/after per event, later changes undone, records isolated, observation-only events, unknown gaps), the image endpoint (200/404/400), and Node tests for the hex-image helper (highlight classes, unknown gaps, ASCII, row limit) and the modal rendering - v2.4.0; SW cache simtrace-analyser-v65
49 lines
1.4 KiB
JavaScript
49 lines
1.4 KiB
JavaScript
const CACHE = 'simtrace-analyser-v65';
|
|
|
|
const PRECACHE = [
|
|
'index.html',
|
|
'manifest.json',
|
|
'icon-192.png',
|
|
'icon-512.png',
|
|
'favicon.png',
|
|
'sim-analyser.svg',
|
|
'sim-analyser-light.svg',
|
|
];
|
|
|
|
self.addEventListener('install', e => {
|
|
self.skipWaiting();
|
|
e.waitUntil(
|
|
caches.open(CACHE)
|
|
.then(cache => cache.addAll(PRECACHE))
|
|
.then(() => console.log('SW: precache done'))
|
|
.catch(err => console.error('SW: precache failed', err))
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', e => {
|
|
e.waitUntil(
|
|
caches.keys()
|
|
.then(keys => Promise.all(
|
|
keys.filter(k => k !== CACHE).map(k => caches.delete(k))
|
|
))
|
|
.then(() => self.clients.claim())
|
|
.then(() => console.log('SW: activated, controlling clients'))
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', e => {
|
|
// Network-first: the local server is always up, so always serve fresh
|
|
// content and fall back to cache only when offline.
|
|
if (!e.request.url.startsWith('http')) return;
|
|
if (e.request.method !== 'GET') return;
|
|
e.respondWith(
|
|
fetch(e.request)
|
|
.then(r => {
|
|
const clone = r.clone();
|
|
caches.open(CACHE).then(c => c.put(e.request, clone)).catch(() => {});
|
|
return r;
|
|
})
|
|
.catch(() => caches.match(e.request))
|
|
);
|
|
});
|