44870bc9412220a7b28e0a519cdb3d95a8df96cf
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5e9dae02c7 |
Complete managed account lifecycle
CI / test (push) Successful in 8m12s
CI / frontend (push) Successful in 4m15s
CI / reuse (push) Successful in 5s
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 9m6s
CI / frontend (pull_request) Successful in 5m17s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m26s
|
||
|
|
34507ffa17 |
Add composable HTTP access roles
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 9m24s
CI / frontend (pull_request) Successful in 5m12s
CI / reuse (pull_request) Successful in 6s
CI / lint (push) Successful in 2m24s
CI / test (push) Successful in 8m8s
CI / frontend (push) Successful in 4m15s
CI / reuse (push) Successful in 5s
|
||
|
|
3c3fc69542 |
Refactor HTTP account system
CI / frontend (pull_request) Successful in 5m13s
CI / reuse (pull_request) Successful in 29s
CI / frontend (push) Successful in 4m15s
CI / reuse (push) Successful in 5s
CI / lint (pull_request) Successful in 2m25s
CI / test (pull_request) Successful in 9m24s
CI / lint (push) Successful in 2m23s
CI / test (push) Successful in 8m19s
|
||
|
|
ee185b98bc |
[feat](trx-logbook): work a contest, and record what came back
Phase 5 of the logbook: the exchange, the entry sponsors take, and the confirmations an award counts. Contest fields go on the contact — the contest, the serials both ways as numbers and as words, and the zones — because an exchange is not always a number: a zone, a section or a name goes in as written. The serial sent and the contest stay between contacts, since they belong to the session and not to the contact just logged, and the serial counts on by itself rather than being retyped forty times an hour. Cabrillo 3.0 is written because ADIF cannot do this job: sponsors take Cabrillo and reject everything else. Its shape is not ADIF's either — the frequency is kilohertz below 30 MHz and a band designator above it, the modes are CW, PH, FM, RY and DG, and the contacts go oldest first, as a contest log is read. The header cannot be derived from a log — how many operators, how much power, what the score is claimed to be — so it comes from the operator, with single-op, low power, all bands and mixed behind it. QSL, LoTW and eQSL states are held as ADIF's single letters, and anything else is refused rather than written: a log that grew states of its own would be one no other program could read. A contact is confirmed when any one of the three says so — an award wants a card or an electronic match, not one of each, and counting them separately would tell the operator they were short of what they have. The bands report counts contacts, distinct stations and confirmations per band, ordered by wavelength as a band plan reads. Closes #54 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7 Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
18b2d0efe6 |
[feat](trx-rs): keep a station log, and a layout to work the bands from
The logbook of issue #54, in the shape the proposal settled on. A new crate, trx-logbook, holds the contact, the ADIF reader and writer, the file, and the rules for telling one contact from two. ADIF because it is the only thing the ecosystem reads: LoTW, eQSL, Club Log, QRZ and every other logger take it and nothing else, so a log that cannot write .adi cannot be uploaded, confirmed or moved. The reader is forgiving in the ways real files are irregular -- lowercase tags, CRLF, missing header, unknown fields, a declared length that is the only thing ending a value -- and carries what it does not model through to the export, so a round trip does not strip what another program wrote. The file is JSON Lines, appended one line per contact. A log is the one thing here that cannot be regenerated, and the bookmark store's whole-file dump would rewrite megabytes to log one contact and lose all of them if the power went halfway; an append costs the record being written and no more, which a test tears a line in half to prove. Edits append revisions, deletes append tombstones, and the file compacts when the superseded outnumber the live. The panel is its own tab and stands in every layout. An entry opens with six fields and no more -- frequency, mode, rig name, time, and the callsign and locator of whatever decode it was started from. A report stays empty: an FT8 SNR is not what was sent. Times come from the server, because the browser may be a phone in another timezone, and the panel says so when the two disagree by more than a second. Worked-before answers as a callsign is typed. A decode is not a contact, so the Log button on an FT8 or APRS row opens an entry and logs nothing by itself. The ham layout is the fifth operator layout, opening on the logbook with the radio controls around it, offered only where the rig can transmit. Two bugs found on the way, both in code written here: a frequency of a whole number of megahertz ending in a zero rendered as a tenth of itself, in Rust and in TypeScript alike, because trimming trailing zeros from "20.000000" walks back through the point. The API also sits under /api/logbook rather than /logbook, so it cannot shadow its own page the way /bookmarks does. Closes #54 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7 Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
a19633e81f |
[fix](trx-frontend-http): rebuild the background decode panel around one list
The panel listed the same bookmarks twice: a status card at the top naming
the selected ones and what each was doing, and a checklist below naming all
of them with the checkboxes. Neither list said which row in the other it
meant, so choosing what to decode and reading what was being decoded were two
passes over the same names. The checklist itself sat squeezed against the
right-hand edge, because the row it lives in sets `align-items: flex-end` and
an inline `flex-direction: column` turned that into "push everything right".
It is one list now. A row carries its checkbox, its name, its frequency,
mode and decoder, and its own state, so picking and watching happen in the
same place. What the rig can hear moves up beside the switch, where it
explains why a selected bookmark reads out of span, and the selection adds
itself up under the list. The states lose the ✓/△/✗ they carried next to a
dot that already said as much, and say what they mean: "Out of span",
"Scheduler has it", "Nobody listening", each with the reason on hover. The
empty list now says which of the two reasons it is empty for, and what to do.
Save was live from the moment the panel opened, with nothing to save; it now
offers itself only when something has changed.
Two races behind it, both of which left the panel useless rather than ugly:
* The rig was whatever the panel was handed at load. Loading before the rig
list arrived handed it null, and the next telling only came when the
operator switched rigs — so the panel stayed empty and silent. Every rig
list refresh now passes the rig on, and both panels ignore one they have.
* The settings panels are wired once the session is up, but their modules
import asynchronously and the wiring skipped whichever had not arrived.
A panel that missed it kept dead buttons for the rest of the session: no
filter, no Select All, no Save. Wiring runs again when the modules land.
Closes #52
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
|
||
|
|
f396e8f235 |
[fix](trx-frontend-http): give satellite passes their own page
Pass predictions were a third view inside the Weather Satellite Decoder card, under Digital modes — a planning tool filed behind a decoder toggle, beside the FT8 and WEFAX panels it has nothing to do with. Nothing about knowing when a bird comes over belongs there. Move them to /satellites, reached from Tools alongside Statistics, Recorder, Settings and About: occasional destinations that live behind that menu rather than taking a slot in the operating strip. Adding a sixth strip button wrapped the phone nav onto two rows and cost the desktop strip its labels at 1280px, so the tab is hidden from the strip exactly the way its four peers already are — the nav is byte-for-byte what it was. The prediction code moves out of sat.ts into its own plugin that loads with the page, so the decoder card no longer carries it. Countdowns stop when the page is hidden and each visit reloads, since passes go stale while it is closed. The server grows a /satellites index route so a deep link or a refresh serves the SPA shell rather than a 404. Closes #47 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7 Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
18107ce07e |
[feat](trx-rs): receive SSTV pictures end to end
CI / lint (pull_request) Successful in 2m16s
CI / frontend (pull_request) Successful in 4m12s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m15s
CI / test (pull_request) Successful in 9m37s
CI / test (push) Successful in 7m36s
CI / frontend (push) Failing after 31s
CI / reuse (push) Successful in 3s
Wires the SSTV decoder into the stack, from the audio the server already has to a panel in the browser that shows the picture arriving. Server: a decoder task alongside the WEFAX one, running whenever the decoder is enabled and the rig is in a mode SSTV is sent in. A finished picture is written to the cache as a PNG and sent on as a message; the rows are sent as they decode, so a client can watch two minutes of Martin M1 fill in rather than waiting for it. Pictures join the decode history, are replayed to a client that connects later, and survive a restart. Protocol: SetSstvDecodeEnabled and ResetSstvDecoder, a sstv_decode _enabled flag in the rig state, two audio message types, and Sstv and SstvProgress on DecodedMessage. The history stores the message without its base64 payload -- the picture is already on disk, and a megabyte per entry is not what a history is for. Client: pictures land in their own history, and the PNG the server sent is written to the local cache so /sstv-images/ can serve it back. That endpoint and the WEFAX one now share their filename checks rather than each carrying a copy: no separators, no parent references, .png only. Web UI: an SSTV sub-tab beside WEFAX, with a live canvas the rows paint into at the line number they carry, a card for the last picture, and a filterable history with links to the files. Rows below the one arriving are grey rather than black -- not yet received is a different thing from received as black. A picture is not a spot, so neither pictures nor their progress updates reach the decode statistics; that exclusion list had grown by hand for LRPT and WEFAX and is now one named set. The decoder crate gains what the server needed to hand a picture on: to_png, to_png_base64 and save_png, with file names stamped in UTC so they sort. Panel behaviour is tested with the plugin runtime: rows painting at their own line numbers rather than in arrival order, a completed picture linked by file name alone with no server path in the page, a cut-off picture reported as partial, clearing, and the toggle following the rig state. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
a2c630a92b |
[feat](trx-frontend-http): put the tuned frequency in the address bar
CI / frontend (pull_request) Successful in 4m3s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m16s
CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 8m11s
CI / test (push) Successful in 7m20s
CI / frontend (push) Successful in 3m10s
CI / reuse (push) Successful in 2s
A receiver spreads by being linked to, and there was nothing to link to: the routes carried the tab and nothing else, so "listen to this" could only ever mean a screenshot and a frequency typed out in a message. The query string now carries the dial -- rig, frequency, mode and bandwidth -- in both directions. Opening a link selects the rig, sets the mode, tunes, then applies the bandwidth: a mode change brings its own default bandwidth with it, so an explicit bw has to land after it. Frequencies are read the way someone writes them by hand (7074k, 14.074M) and written back as whole Hz, so what comes out of the address bar is the same link in canonical form. After that the address bar keeps up with the dial, which is what makes it copyable at any moment rather than only at load. It is rewritten with replaceState -- tuning is not navigation, and a swept dial would otherwise bury the back button. A link button in the top bar copies the current link; it folds into the overflow menu when the bar is tight. Applying a link changes the radio, so an rx session says so instead of failing control calls one at a time. A tab listening to a virtual channel leaves the address alone rather than publishing a frequency the rig is not on, and bw is skipped in both directions on rigs without filter control, which would only refuse it. The fixture pinned every state frame to 100 MHz plus jitter to keep frames distinct, so no test could observe tuning at all. The jitter moves to the S-meter and the fixture echoes set_freq/set_mode/ set_bandwidth, as it already did for squelch. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b48cc23d6e |
[fix](trx-frontend-http): keep the rig names through the state stream
The picker and the header showed each rig's lowercase id instead of its configured name. applyRigList takes the names as a parameter defaulted to an empty map, and the state-update path passes only the rig ids — names come from /rigs, not from a state frame — so that call landed on the default and the body, which treats "an object" as "here are the names", cleared them. One frame after load the names were gone for the rest of the session. Omitted now means no news rather than no names. The fixture is why this was invisible: it pushed an identical status payload every tick and the client skips a frame equal to the last, so render never ran and neither did the call that did the damage. Its event stream varies between frames now, as a real one does. Which immediately caught a second fault: state frames arrive continuously, and one sent before the server applied a new squelch threshold snapped the line back to where it had just been dragged from. A local change outranks the echo for two seconds, the same idea as the optimistic frequency guard beside it. The fixture also records what /set_sdr_squelch sets and reports it back afterwards — the drag test had been passing against a server that ignored the write. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
c1899229a0 |
[fix](trx-frontend-http): stop the decode history replay giving up at 20s
Reloading a second time sometimes showed history the first load did not, and the safety valve is why: it called one function that both released the buffered live decodes and tore the history worker down, so any load where the replay had not finished inside twenty seconds — a large backlog, a cold cache, a slow link — dropped whatever had not arrived, without a word. A reload got another go at it, and the second one is faster because everything is cached by then. Those are two separate things now. At the timeout the live decodes are released so the panels are not held back, the replay carries on, and the progress says so. The fallback's error path retries once and then says "Decode history unavailable" rather than leaving the operator to guess whether there was anything to see. The progress is no longer a scrim. It was fixed to the whole viewport with a wash over the page — the waterfall, the decode panels, all of it — for the length of the replay, which is exactly when there is something worth watching. It is a corner card with a bar: indeterminate while the payload is on the wire, then filling as N of M messages replay. None of this was reachable from a test. /decode/history answers in CBOR and the worker reads the body as CBOR unconditionally, but the fixture served JSON, so every browser run had been exercising the client's retry path and never its history path. It encodes CBOR now, including the 64-bit form the millisecond timestamps need, and decode-flow serves 1200 records and holds the client to restoring all of them on the first load, showing progress while it does, and never covering the page with it. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
84a99a3636 |
[fix](trx-frontend-http): load the decoders that own the digital modes panels
AIS, VDES and both APRS decoders were listed under the map plugin group alone, so opening Digital modes and clicking AIS or APRS gave an empty panel reading "Connected, listening for packets" while the decodes piled up unprocessed in the plugin runtime. They appeared only if something had opened the Map tab first, which flushed the queue. There is also a map-data group naming exactly those four that nothing loads: the loader is called with tab names and no tab is called map-data. They load with the tab whose panels they fill now. map-core stays lazy, since their calls into it are optional and the Map tab can go on paying for Leaflet by itself. tests/decode-flow.mjs follows a decode from the wire to the map: an AIS vessel and an APRS beacon arrive on /decode, and it asserts both panels fill with the map module confirmed absent, the mini view names the vessel and offers a pin, following that pin lands on /map centred on the vessel, and both decoders leave a marker. Nothing exercised any of this before — the fixture served an empty decode stream, which is how the map links came to be broken for every decoder at once. The fixture stamps decodes as it sends them, since the client prunes anything outside the retention window, and repeats them, since the views collapse by vessel and need more than one frame to behave. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
2c1df75d19 |
[fix](trx-frontend-http): measure auto squelch from the meter
Auto took the spectrum's noise floor and added 6 dB, but the threshold is compared against the channel level the meter reports, and the two sit a long way apart: the gap is set by the FFT size and window, the channel bandwidth, the decimation, and peak-versus-mean statistics. Measured on white noise it runs +22.1 dB at 48k/8k/3k, +18.7 dB at 240k/24k/12k and -1.2 dB at 1.92M/24k/12k — a 23 dB swing across ordinary configurations. Only the last of those is anywhere near right, so on a narrow span Auto set the gate some 20 dB below the noise and it never closed. It now reads the same number the DSP compares: the 20th percentile of the meter over the last ten seconds, plus 5 dB. The percentile keeps a burst of traffic inside the window from dragging the estimate up, and 5 dB clears the meter's own jitter, which measured 0.9-1.6 dB. Nothing in it converts between scales, so no part of the signal chain can put it out again. With no history yet — a fresh connection, a rig switch — it listens for a moment rather than refusing. The fixture gained a streaming /meter, without which there is nothing to measure, and the spectrum test pins auto to the meter it serves. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
aefd36c4b1 |
[feat](trx-frontend-http): set the squelch on the spectrum, in dB
The threshold is in dB, and since the squelch fix that is the scale the spectrum axis and the S-meter are labelled in — so the control belongs on the plot, at the level it gates. A dashed line spans the spectrum at its threshold with a grip that reads it out, dragged like the bandwidth edges, green while the signal is above it and amber while it gates. Arrow keys move it a dB at a time for anyone not using a mouse. The audio row keeps a compact version: the dB, an indicator lit from the same meter the DSP compares against, Auto, and an enable toggle that no longer doubles as the level. The slider ran 0-100% over that dB range, which gave the operator a number with nothing on screen to relate it to, and zero meant "disabled", so turning the squelch off to listen threw the threshold away. Auto now says which level it picked. Two things the browser could only show once it was on the plot: the grip landed underneath the split control at the right edge, which swallowed its pointer, and dragging to the foot of the axis hid the line — and the grip with it — instead of pinning it where it could be dragged back. The fixture could not exercise any of this: /audio answered 404, which hides the audio row and the control inside it, and the status carried no filter block, which is what tells the client the rig has a squelch at all. Both now look like an SDR, and the spectrum test drives the line. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
4fa191e9b6 |
[fix](trx-frontend-http): serve the band plan to every session
/bandplan.json needed the control role. Route access is decided by suffix for static assets — .js, .css, .png and so on — and ".json" is not among them, so the band plan matched nothing and fell through to the catch-all. It is compiled into the binary and identical for every user, so it is public now, like the rest of them. Two things followed from that. Read-only sessions never saw a band plan at all. And since the page asks for it during startup, the request can land before the session is established: that 401 was swallowed by an empty catch and never retried, which is why the allocations sometimes only appeared after a manual reload. So the client no longer hides the failure, retries once the auth gate clears — which is exactly when a startup 401 becomes fixable — and schedules a draw when the data lands, since the strip is painted from the spectrum draw and a rig sitting between frames would stay blank. The fixture can now refuse the first request the way the server did, and the spectrum layout test holds the client to recovering from it. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
e70e82c8c0 |
[feat](trx-frontend-http): rebuild the general radio controls row
Mode was a full-width select: 483px of the row to display "FM". The modes are three or four characters and there are at most twelve, so they become a segmented group like the Unit and Step Scale pickers beside them — a third of the width, and one click instead of two. The <select> stays as the mode's value. A dozen call sites and several plugins read #mode.value, so replacing it outright would have reached much further than a layout change should; it is hidden from sight and from assistive tech, the buttons write to it, and everything downstream runs unchanged. Every writer re-syncs the buttons, the plugins through a new trxCore.syncModePicker. The row itself was a grid with a track per column, but the WFM, SAM and transmit columns are hidden on most rigs, so it ended in some 500px of hole. It packs left now. Same fault one level down: the power buttons sat in three fixed tracks, so a rig with neither transmit nor lock kept two empty ones and left its label chip stranded at the far edge. Unit and Step Scale move out of the frequency row and in beside the wheel and the +/- they modify, which were some 600px away. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
1f256cbb68 |
[refactor](trx-frontend-http): extract the browser test fixture
browser-smoke.mjs carried its static server inline, which made it the only browser test that could exist: a second one would have had to copy 180 lines of routes to change a single capability flag. The server moves to tests/web-fixture.mjs behind startWebFixture(), with the rig's spectrum support, bookmarks and band plan as options. Serving a rig with a spectrum matters because that is where the layout actually lives — the panel, the strips above it and the waterfall are all gated on filter_controls, and the existing fixture reports a CAT-only rig, so none of it has ever been rendered under test. No change to what the smoke test checks. Signed-off-by: Stan Grams <sjg@haxx.space> |