feat: RAM operations run behind a blocking modal (v3.6.34)

A CAP install is a long chain of LOAD commands whose progress and results
were only visible at the bottom of a large form - and an interrupted
install can leave a half-loaded package.

- Server: _RAM_PROGRESS (+ _ram_progress_begin/step/end) records the live
  step of the running RAM operation; /api/ram-install and
  /api/ram-install-app wrap their chains in it (total = steps + 1 for the
  format check) and /api/status carries it as ram_progress - lock-free, so
  the 2 s poll works while the request holds _CARD_LOCK.
- PWA: #ram-op-modal (the STK panel pattern) blocks everything while a RAM
  operation runs: title, spinner, status line, progress bar, per-step
  result lines, Close disabled until it finishes; a beforeunload guard
  protects the window during the run.  install-cap, install-app /
  make-selectable and Explore all use the shared helpers
  (ramOpModalBegin/Status/Finish); the install results stay in the form
  area as before once the modal is closed.  The status poll feeds the modal
  via ramOpProgress (headline from the pure ramOpProgressText).
- Tests: ramOpProgressText (JS) + the progress lifecycle (Python); the
  card-state and capmem suites stub the DOM-bound hooks.
- Stylesheet rebuilt; version 3.6.34; sw simple-v307.
This commit is contained in:
2026-09-29 00:09:35 +03:00
parent a935361ca6
commit 0b7b6b8157
8 changed files with 231 additions and 46 deletions
+17
View File
@@ -1588,3 +1588,20 @@ class RamCommandFormatTests(unittest.TestCase):
# only the accepted (expanded) probe advanced the 5-byte counter
self.assertEqual(state['cntr'], '10000AAAC9')
self.assertEqual(len(state['steps']), 2)
class RamProgressTests(unittest.TestCase):
"""Live RAM-operation progress exposed in /api/status (v3.6.34)."""
def test_progress_lifecycle_and_payload(self):
from pysim_simple_server import server as srv
srv._ram_progress_begin('install-cap', 5)
p = srv._ram_progress_payload()
self.assertTrue(p['active'])
self.assertEqual((p['kind'], p['step'], p['total']), ('install-cap', 0, 5))
srv._ram_progress_step(3, 'LOAD (2/3)')
p = srv._ram_progress_payload()
self.assertEqual((p['step'], p['name']), (3, 'LOAD (2/3)'))
self.assertGreaterEqual(p['elapsed'], 0)
srv._ram_progress_end()
self.assertFalse(srv._ram_progress_payload()['active'])