83b453135a81ca85d9e7d1f99d733ced26906087
197
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2c56d82a81 |
[feat](trx-frontend-http): make the SQL label the squelch switch
Clicking SQL turns the squelch on and off. The label and the button beside it said the same thing twice — one naming the control, the other reading "On" or "Off" — where the name itself is the obvious target, and the dot already carries the state: grey when off, green while the gate passes, amber while it holds. The pressed state is on the label, so the switch reads the same to a screen reader as it looks. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
0fc2115973 |
[fix](trx-frontend-http): make map links work before the map has loaded
The AIS and APRS mini views link each position to the map, and neither did anything: the map module installs itself lazily, and it was the one defining window.navigateToAprsMap, so until something had opened the Map tab the global did not exist. AIS calls it inline from onclick and threw "not a function"; APRS guards the call and so failed silently. The grid links on FT8, FT4, FT2 and WSPR rows went the same way through navigateToMapLocator. The app owns both globals now, installed at startup. They record the target, switch tabs through navigateToTab — the only path that materialises the panel from its template, loads the module and updates the history entry, none of which the module's own hand-rolled tab switch did — and the target is applied once the module reports ready. The module keeps the focusing, which is its job, and exposes it as focusMapPosition and focusMapLocator. The smoke test now calls the link from a cold page, asserting the map module is not loaded first so the check cannot pass by accident. 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> |
||
|
|
25b9c31c9b |
[fix](trx-backend-soapysdr): squelch against the meter, not the post-AGC level
The threshold was compared against the block level measured after the IQ AGC. Holding that level at a setpoint is the AGC's entire purpose, so for every mode that has one — FM, PKT, AIS, AM, SAM, which is to say the modes anyone squelches — the comparison was against a near-constant. With FM's 12 dB of gain a weak signal reads some 12 dB hotter than it is, and the value never had the decimation correction the meter applies on top of that: around 20 dB adrift at 48k/8k, more as decimation grows. The threshold arrives in the other scale entirely. The slider maps its percentage onto -120..-30 dB and Auto takes the spectrum noise floor plus 6, both of which are what the meter and the spectrum display show. So a gate set just above the noise sat open on it. It now reads last_signal_db, which is already computed each block before the AGC and corrected for decimation — the same number the meter shows. The post-AGC measurement had no other consumer. The test feeds one signal twice and takes the threshold from the channel's own meter: 6 dB above must gate it, 6 dB below must pass it. Nothing there depends on the absolute scale, only on the two agreeing. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
d68a84f7f9 |
[chore](trx-client): apply cargo fmt
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> |
||
|
|
c073d03ffb |
[feat](trx-frontend-http): reorder the controls tray sections
The radio's own settings now come first, then audio, then the scheduler: Advanced radio controls, Audio controls, Scheduler controls. The advanced section is not in the markup — ui-core builds it at runtime and gathers the SDR settings, virtual channel and TX limit rows into it, appending the result, which put it last however the markup was ordered. It is inserted ahead of the audio section instead. The signal readout and the TX meters stay where they are, between the controls and the sections: they are readouts rather than a section, and on an SDR the spectrum covers them anyway. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
730f129404 |
[fix](trx-frontend-http): let the header identity give way before the tabs
CI still put the tab strip into the controls, now at 1100px — the narrowest bar in the app, since the bookmark gutters take 9.5rem a side above that width and leave 756px against 871px at 900px. With the controls already in the overflow menu and the tabs already down to icons, nothing else could give, and what gives by default is the strip: it is the one item allowed to shrink below its content, so its tabs keep full width and slide under the controls, out of reach. The identity block takes the squeeze instead, ellipsised. A clipped station name is still readable; a destination hidden underneath the controls is not. The guard that was supposed to catch this scaled only the tabs and the controls, not the title and subtitles — which is exactly what runs out of room — and skipped 900px. It now scales every piece of text in the bar and checks all four widths. Measured across text scales from 1.0 to 3.0 at each width, the bar keeps its 16px allowance everywhere; before this, 1.6 and above overlapped at 1100px. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
f95f3d0104 |
[feat](trx-frontend-http): centre the radio controls, split off the mode row
The controls every rig has — mode, wheel, tune step, transmit — now sit as a centred block rather than packed against the left edge. What the current mode adds moves out from among them: WFM's six controls stretched the row sideways whenever it was active, pushing the wheel and the step pickers off centre, and SAM did the same on a smaller scale. They get a row of their own below a divider, which appears and leaves with them — an empty one would still take a track and a gap in the tray and draw its divider under controls it has nothing to do with. 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> |
||
|
|
23cc0db1fa |
[fix](trx-frontend-http): drop the tabs to icons when the bar runs out of room
CI put the tab strip 9px into the controls at 1440px on a run that passed locally: its system font is wider, and the bar had no move left to make. Controls are moved into the overflow menu until the bar fits, but once all of them were in the menu nothing else gave — the nav may shrink below its content, so the tabs kept full width and ran under the controls, leaving the destinations nearest them unclickable. Labels dropping to icons was the other half of the answer, but it hung off a max-width:1360px media query and so was unavailable at 1440px. That class now goes on by measurement, as the last step after the menu is exhausted, which is the same reasoning the controls' own fit test already uses: how much fits depends on the rig name and on how wide the platform draws the labels, not on the viewport. The class is cleared before measuring so the decision cannot ratchet, and icon widths are fixed, so it always buys back the labels' width. Labels now stay put between 1100px and 1360px while they fit, with the style picker and theme toggle behind the overflow menu instead. The suite could not have caught this: it passed on the fonts of the machine that wrote it. The layout section now repeats its fit check with the bar's text scaled up, which reproduces a wider system font anywhere — with this fix reverted it fails on macOS too. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
657f952a2c |
[fix](trx-frontend-http): hold the strips' place above the spectrum
Tuning across a band edge moved the whole page under the cursor: the band plan strip is in flow, so a range with no allocations collapsed it from 18px to nothing and dragged every element below it up (measured at 1600x950: overview top 118 to 100, footer 1026 to 1008). It now keeps its height whenever a band plan could be drawn at all, and only gives it back when the feature is off, has no data, or there is no spectrum. The bookmark rail gets the same treatment for consistency, though it never moved anything — it is absolutely positioned over the top of the overview. It stays up and blank rather than vanishing. Which exposes something the rail was already doing wrong: it covers the top of the plot, and a bare div still takes pointer events, so whenever bookmarks were in range that band of the overview could not be dragged or scrolled. Only the chips are targets now. tests/spectrum-layout.mjs covers this: it streams spectrum frames, tunes between a band with bookmarks and allocations and one with neither, and asserts nothing moves and that the rail lets clicks through. 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> |
||
|
|
92fbdb692c |
[feat](trx-frontend-http): lay the scheduler controls out in one row
The controls were a column — release, then the step buttons, then the status line, then the entry on air last — which read bottom-up and left the entry that is actually transmitting furthest from the buttons that change it. They now run left to right: step through the entries, hand the rig back, then the current entry behind a separator. The separator is a pseudo-element on the current-entry block rather than an element of its own, because that block is display-toggled whenever fewer than two entries are active; a standalone rule would be left hanging with nothing after it. No ids move, so the enable/disable logic in the scheduler plugin and the release polling in vchan bind exactly as before. The smoke test asserts the row's order, which is also what keeps the separator in place. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b2fbcb318d |
[fix](trx-frontend-http): stop Tools lighting up on every refresh
navigateToTab marked the Tools button by asking whether the destination tab was displayed, which is the right question at the wrong moment: the first route navigation runs while the card is still behind the loading state, where every tab computes to display:none. Refreshing or deep linking to any page therefore lit Tools alongside the real destination, and nothing re-evaluated it once the page appeared. Membership of the Tools menu answers the same question without needing anything laid out, and still reads the grouping ui-core installs rather than a second copy of it. The smoke fixture now serves the SPA shell for route paths the way the server's per-tab index handlers do, so a deep link no longer 404s and the case is testable at all; two of them are asserted. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
44721b579c |
[fix](trx-frontend-http): fill the map column down to the footer
The windowed map was capped at 75% of the viewport height and at a width-derived aspect ratio, which left a dead band under it: 69px at 1600x950, and on a 420px-wide phone a 270px map on an 800px screen. Neither cap was doing useful work now that the stage spans the full width, so the map fills the column down to the footer instead. Growing into the footer needs a bound: once the column is tall enough to push the footer below the fold, using its position would push it further on every pass, so the bottom edge is clamped to the viewport. Growth then consumes the column's spare height and settles in one pass. Also drops three mapIsFullscreen() branches in the windowed path that could never be taken — the fullscreen case returns above them. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
889d00007d |
[feat](trx-frontend-http): box the selected tab instead of underlining it
The desktop strip marked the current page with a 2px underline while the mobile bottom nav already boxed it, so one navigation model looked like two. The box now sits on both: a transparent 1px border on the base reserves it, so switching pages moves no neighbours, and hover fills a fainter version of the same shape. Tools carries it too — that button is marked active for the destinations the strip hides. Dropping the mobile rule's border-bottom:none, which only existed to cancel the old desktop underline, closes the bottom edge its active box had been missing. The smoke test checks all four edges. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
6f53592041 |
[feat](trx-frontend-http): rework the page footer
The footer floated in space below the content with no rule to close the page, its two clusters sat on a text baseline that left the source pill hanging, and the status hint was a plain line of text a size larger than the attribution beside it. Now a hairline closes the page the way .tab-bar opens it, the clusters centre on one line, and the attribution drops the opacity it stacked on top of --text-muted, which had put it below a readable contrast ratio. The status hint becomes a pill with a state dot: green when ready, amber while a command is in flight, red on connection loss. The colour comes from a data-state attribute, so every hint now goes through setPowerHint instead of assigning textContent directly. --status-ok carries the indicator green; .about-status-on picks it up too, which darkens it on light themes where the old value was barely legible. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
709939f60b |
[feat](trx-frontend-http): span the map across the full viewport width
The map stage broke out of the centred .card column: negative inline margins cancel the card's centring offset and its side padding, so the stage reaches both viewport edges at every width without hardcoding either value. Its rounded corners and left/right borders go with it — edge to edge, the panel reads as a band rather than a floating card. The browser smoke test now measures the stage against the viewport, and checks that the full-bleed width does not push the page sideways. Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
5b7dd493d4 |
[feat](trx-frontend-http): draw APRS symbols from the sprite sheets
CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 8m12s
CI / frontend (pull_request) Successful in 3m2s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m24s
CI / test (push) Successful in 7m28s
CI / frontend (push) Successful in 2m11s
CI / reuse (push) Successful in 3s
Resolve a table/code pair to a sprite cell in aprs-shared, and use it from both the packet lists and the map markers, which had each been printing the raw symbol character in a bordered box. A table identifier of / or \ selects the primary or alternate sheet directly. Anything else is an overlay character, which the APRS spec draws on top of the alternate symbol -- so those stack the overlay sheet over the alternate one rather than picking a sheet. Codes outside 0x21..0x7E have no cell and keep the old character box. The sheet URLs stay in the stylesheet so a min-resolution query can swap in the retina sheets; only the cell offset is computed and set inline. Map markers share the helper through the plugin chunk, so the map stays free of any remote symbol fetch. Verified in a browser against the real stylesheet and sheets: /> is a car, /_ a WX circle, /& an igate diamond, \n a red triangle, and the overlays S> and 7# carry their character on the alternate symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018huL1ELyr86yVqfAabtioA Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
02fe492dbf |
[feat](trx-frontend-http): serve the vendored APRS symbol sprites
Embed the six sheets alongside the other vendored assets and serve them under /vendor with the same immutable cache headers. The browser computes a symbol's cell from a 16x6 grid of 24px cells, so a re-vendored sheet at any other size would shift every station onto a neighbouring icon -- wrong on every packet, and invisible unless you know which glyph to expect. Pin the geometry by parsing each embedded PNG's IHDR in a test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018huL1ELyr86yVqfAabtioA Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
1bc2c88e2f |
[chore](trx-rs): vendor the APRS symbol sprite sheets
The web UI had no symbol graphics at all, so a position report rendered its raw symbol character in a bordered box. Vendor rev H of the hessu/aprs-symbols set: three 24px sheets (primary, alternate, and the overlay characters) plus their retina variants. The set carries no single license. Individual symbols are variously vectorizations of the original WA8LMF bitmaps with unknown terms, new CC BY-SA work by OH7LZB, public-domain or CC sources, and a handful of brand logos owned by their companies. Record that as LicenseRef-APRS-Symbols with the upstream per-symbol catalogue copied verbatim, and carry the attribution pointer upstream asks for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018huL1ELyr86yVqfAabtioA Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
863a6d8fd4 |
[fix](trx-frontend-http): restore decode history from the stored records
CI / lint (pull_request) Successful in 2m21s
CI / test (pull_request) Successful in 8m19s
CI / frontend (pull_request) Successful in 3m1s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m16s
CI / test (push) Successful in 7m26s
CI / frontend (push) Successful in 2m11s
CI / reuse (push) Successful in 2s
Replay required every restored record to carry a string `type`, and stored records do not have one: an AIS entry holds mmsi, lat, lon, crc_ok and its decoder's own fields, nothing more. The filter therefore discarded all of them, and did it silently — the fetch returned its full payload, the worker decoded it, and no error was logged, so the history simply never appeared. That field identifies live SSE frames, which do carry it, which is why only replay was affected. History arrives already grouped and the group's kind is delivered alongside the messages, so `type` was never needed to route them. Require only that a record is an object. Confirmed against a live server: the first restored group is AIS, and its records expose their decoder fields with `type` undefined. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b252717f6b |
[test](trx-frontend-http): serve a realistic decoder registry to the smoke test
CI / lint (pull_request) Successful in 2m18s
CI / test (pull_request) Successful in 7m25s
CI / frontend (pull_request) Successful in 2m10s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 7m26s
CI / frontend (push) Successful in 2m12s
CI / reuse (push) Successful in 3s
The fixture answered /decoders with an empty list, which hid most of the application from the only test that runs it in a browser. The decoder sub-tabs, their panels, the decode toggles and the bookmark decoder checkboxes are all built from that registry, so the run exercised three of thirteen sub-tabs and none of the decoder UI. Finding this needed route interception, because nothing in the suite could see it. Serve eleven decoders covering the modes the real registry spans. The run now builds 13 sub-tabs and 11 bookmark decoder checkboxes — the same checkboxes whose construction a recent fix changed without any test reaching them — and still reports no runtime errors. It also makes an existing fault observable: at 1100px the decoder sub-tab bar hides 195px of itself with no scrollbar or fade, the same silent truncation the top strip had. No assertion for it here, since that would fail until the truncation is fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
92697b11c5 |
[feat](trx-frontend-http): mark Tools active for its destinations
CI / test (pull_request) Successful in 8m9s
CI / test (push) Successful in 7m22s
CI / lint (pull_request) Successful in 2m15s
CI / frontend (pull_request) Successful in 3m1s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m16s
CI / frontend (push) Successful in 2m10s
CI / reuse (push) Successful in 2s
Grouping Statistics, Recorder, Settings and About behind Tools left the tab strip looking identical on all four: the destination's own button carries the active class, but the strip hides that button, so nothing was marked. The page titles named the page without saying how you got there. Mark the Tools button when the active destination is one the strip hides. That state is read from the button's computed display rather than from a second copy of the grouping, so the two cannot drift: whatever ui-core puts in the menu lights up Tools, and a destination promoted back into the strip stops doing so with no further change. Tools already carries the tab class, so the existing active styling applies unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b409c57296 |
[test](trx-frontend-http): assert header geometry in the browser smoke test
CI / test (push) Successful in 7m27s
CI / frontend (push) Successful in 3m1s
CI / reuse (push) Successful in 3s
CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 7m24s
CI / frontend (pull_request) Successful in 2m11s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m15s
Several layout faults shipped while every gate passed, because nothing looked at geometry: a header whose height tracked the viewport, controls at four different heights, a tab strip that ran under the controls, and a dropdown that opened underneath the page. Assert the invariants behind those at four widths — the header stays one row, the tabs do not reach the controls, the controls share a height, the page does not scroll sideways — and that the menu renders with real dimensions and wins a hit test at its own centre. The overlap check measures the tabs rather than the strip: with the strip allowed to overflow, its box shrinks while its content paints across the controls, so the container's own rect never registers the collision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
00191c8d7a |
[fix](trx-frontend-http): serve the Statistics and Bookmarks routes
CI / lint (pull_request) Successful in 2m18s
CI / frontend (pull_request) Successful in 3m1s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m17s
CI / test (pull_request) Successful in 8m23s
CI / test (push) Successful in 7m28s
CI / frontend (push) Successful in 2m9s
CI / reuse (push) Successful in 2s
The server answers /, /map, /digital-modes, /recorder, /settings and /about with the application shell, but never had a route for /statistics or /bookmarks. Both fell through to the catch-all asset handler, so reloading on either one downloaded a file instead of reopening the page. Only in-app navigation worked, which is why it went unnoticed until Statistics was reachable from the Tools menu. Add the two missing shell routes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
56c363a517 |
[fix](trx-frontend-http): align the Statistics page and unblock the tab strip
Two faults, both mine, both visible in one screenshot of that page. #tab-statistics was the only panel with padding of its own, so its title and content sat 16px inside where every other page begins. Remove it and the page lines up with the header and with its siblings. Removing the tab strip's `overflow-x` left it unable to shrink below its content, so at around 1280px it ran under the controls: the Map tab sat beneath the audio button and Tools beneath REC. Clipping is safe again — the menus it anchors are reparented to the body when they open — so the strip can shrink, and the labels now give way to icons at 1360px rather than 1180px, before it has to clip anything. Measured at 1280px: 321px of clearance between the strip and the controls, and the page title at the same left edge as the header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
5ad91b4ab6 |
[fix](trx-frontend-http): stop doubling the space under the Statistics title
CI / lint (pull_request) Successful in 2m17s
CI / test (pull_request) Successful in 8m13s
CI / frontend (pull_request) Successful in 3m1s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m20s
CI / test (push) Successful in 7m33s
CI / frontend (push) Successful in 2m12s
CI / reuse (push) Successful in 3s
The page titles carry a bottom margin, which is what spaces them from the content on the plain block panels. #tab-statistics is not one: it is a flex column with `gap: 1rem`, so the margin landed on top of that gap and left 28px under the title where every other page had 12px. Drop the margin on that panel and let its own gap do the spacing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
1843522b45 |
[feat](trx-frontend-http): give the Tools destinations page titles
CI / lint (pull_request) Successful in 2m21s
CI / test (pull_request) Successful in 8m13s
CI / frontend (pull_request) Successful in 2m59s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m21s
CI / test (push) Successful in 7m30s
CI / frontend (push) Successful in 2m10s
CI / reuse (push) Successful in 3s
Recorder stated its name; Statistics, Settings and About did not, so one page in eight carried a title. The class it used, section-heading, had no rule behind it either, leaving even that title as a default h2. Which way to unify follows from the navigation change. The tab strip highlights the destination you are on, so Radio, Bookmarks, Digital modes and Map already say where you are and a title would repeat the strip while costing vertical space the spectrum wants. The four destinations behind Tools get no such highlight — the strip looks the same on all of them — so those are exactly the pages that have to name themselves. Give the three that were missing a heading, and style section-heading so all four match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
41ddecf5a7 |
[fix](trx-frontend-http): label the overflow tab Tools and hide its glyph
CI / test (pull_request) Successful in 8m17s
CI / lint (push) Successful in 2m18s
CI / lint (pull_request) Successful in 2m16s
CI / frontend (pull_request) Successful in 3m6s
CI / reuse (pull_request) Successful in 3s
CI / test (push) Successful in 7m25s
CI / frontend (push) Successful in 2m11s
CI / reuse (push) Successful in 3s
The button rendered as "•••More": the dots span carried no styling at all, so the glyph sat flush against the label instead of behaving like the icon it is. Every other tab hides its icon while labels are shown and swaps to it when they are not; the dots now follow the same rule, so the button reads "Tools" beside the other labels and becomes the glyph alone in the icon band. "More" also said nothing about the destinations behind it. The menu holds Statistics, Recorder, Settings and About, so name it Tools and give the button an aria-label that spells that out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b06c37affa |
[fix](trx-frontend-http): lift the header menus out of the header
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 8m21s
CI / frontend (pull_request) Successful in 3m5s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 7m36s
CI / frontend (push) Successful in 2m9s
CI / reuse (push) Successful in 3s
Fixed positioning escaped the clipping, but not the stacking: the header carries `z-index: 2`, which makes it a stacking context, so whatever z-index a menu inside it carries is confined below level 2. The spectrum overlays paint as high as 9600, so both dropdowns opened underneath them. Reparent each menu to the body when it opens. Leaving that subtree is the only way out of an ancestor's stacking context, and the menus are already positioned in viewport coordinates, so nothing else about them changes. The outside-click test now considers the menu as well as its wrapper, since the two are no longer nested. Verified by hit testing rather than by inspecting z-index: elementFromPoint at the open menu's centre returns the menu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b4912f5879 |
[fix](trx-frontend-http): render the header menus above the page
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 7m23s
CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 8m13s
CI / frontend (pull_request) Successful in 2m59s
CI / reuse (pull_request) Successful in 3s
CI / frontend (push) Successful in 2m10s
CI / reuse (push) Successful in 3s
Both header dropdowns were laid out inside the bar rather than over the page. The navigation menu opened as an 18px sliver positioned above its own button, and the overflow menu did not appear at all. Two causes. The tab strip kept `overflow-x: auto` from when it scrolled, which clips an absolutely positioned descendant — and the strip is what the navigation menu anchors to. The strip no longer scrolls, since the occasional destinations moved behind More, so the property and the edge fade that went with it are both gone. Anchoring in fixed coordinates at open time addresses the general case: an absolutely positioned menu is clipped by any scrolling ancestor and trapped inside whatever stacking context its ancestors create, so it can be squashed inside the bar or painted underneath page content. Fixed coordinates answer to the viewport, and the menu flips above its button near the bottom edge. Clearing `right` when setting `left` keeps the menus at their natural width: the stylesheet pins them to the right of their anchor, and leaving that in place stretched them across the bar — 845px for a four-item list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
27f2558193 |
[feat](trx-frontend-http): one navigation model at every width
CI / test (pull_request) Successful in 8m13s
CI / frontend (pull_request) Successful in 3m0s
CI / reuse (pull_request) Successful in 3s
CI / test (push) Successful in 7m25s
CI / lint (pull_request) Successful in 2m18s
CI / lint (push) Successful in 2m17s
CI / frontend (push) Successful in 2m9s
CI / reuse (push) Successful in 3s
Eight destinations sat flat in the tab strip with equal weight, competing with the controls for the same row and then scrolling out of reach with only a fade to say so. They are not equal: Radio is where an operator spends nearly all their time, Bookmarks, Digital modes and Map are operating surfaces, and Statistics, Recorder, Settings and About are occasional. The mobile layout already grouped them exactly that way, behind its More menu, so the application carried two navigation models. Adopt the mobile grouping at every width instead of adding a third: four operating tabs plus More. The strip no longer scrolls at any width, and the menu keeps its bottom-sheet placement on mobile while anchoring under its button elsewhere. Drop the labels between 701 and 1180px so the tabs degrade to their icons — which every tab already carries — before the strip could ever need to hide a destination. Rename Main to Radio: it is the receiver, not a generic first page, and the name now says what the destination is rather than where it sits. Freeing that width also let the style picker and theme toggle return to the bar inline, leaving only the layout picker in the overflow menu. Navigating to About in the browser smoke test now goes through More, as a person would. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
3a9bf1b7ce |
[fix](trx-frontend-http): drop the rig description from the top bar
The header repeated the active rig's hardware string and mode list beside the rig picker. With a real SDR that reads SoapySDR driver=airspyhf,serial=c852eb5dd23539f8 · RX · SDR filters · LSB · USB · CW · CWR · AM · +7 modes which is longer than every other control in the bar combined, and it is already on the About tab in full, split across its Rig, Active rig, Connection, Modes and VFO rows. Remove the element and the builder behind it. Rig switching keeps its feedback through the existing hint channel rather than by briefly rewriting a permanent label, and the identity that belongs in a header — the rig's display name — stays in the left subtitle. The freed width is not spent: the tab strip now reaches Settings before it needs to scroll, where it previously faded out during Statistics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
dd5760c436 |
[style](trx-frontend-http): fade the scrolled tab strip edge
CI / lint (pull_request) Successful in 2m19s
CI / test (pull_request) Successful in 8m17s
CI / frontend (pull_request) Successful in 3m0s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 7m33s
CI / frontend (push) Successful in 2m9s
CI / reuse (push) Successful in 3s
The page tabs scroll rather than wrap, so the last visible tab was sliced
mid-word ("Se…" for Settings), which reads as a rendering fault instead of
as an invitation to scroll.
Fade the trailing edge with a mask. A colour-matched cover gradient is
the usual trick, but the card is transparent, so a cover would have to
track the page background across both themes and all nine styles; a mask
is colour-agnostic. Only the trailing edge is faded, leaving the first
tab crisp while the strip sits at rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
Signed-off-by: Stan Grams <sjg@haxx.space>
|
||
|
|
eee3630f04 |
[feat](trx-frontend-http): compact single-row top bar
The header's height depended on the viewport width, and not even monotonically: 112px at 1440, 169px at 1100, 131px at 900, 246px at 720. Both control groups wrapped, so every width produced a different ragged block — eight page tabs across four rows at 1100px, and action controls across three. Four different control heights (32, 34, 45 and 54px) sat in the same row, the 54px one being the rig picker with its summary stacked underneath, and on narrow viewports the icon buttons stretched to fill half the row, rendering a play triangle centred in a 249px box. Lay both groups out as one row that never wraps. Controls are a uniform 2rem and no longer stretch, the rig summary sits inline beside its select, and the page tabs scroll instead of wrapping. Secondary controls — layout, style and theme — move into an overflow menu when the bar cannot hold them, leaving audio, record and the rig picker inline. Deciding when they no longer fit needs natural widths, not rendered ones: the nav has min-width 0 and scrolls, so it always shrinks to the leftover space and always reports scrolling, and the bar reports overflow even when nothing is clipped. scrollWidth on the scroll container is its unconstrained content width, which is what the fit test compares against the space available. Measured after the change: 72px at 1440, 1280, 1100, 900 and 480, every control 32px, nothing clipped at any width. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
b31790ff48 |
[fix](trx-frontend-http): keep layout sections togglable
CI / lint (pull_request) Successful in 2m21s
CI / frontend (pull_request) Successful in 3m3s
CI / reuse (pull_request) Successful in 3s
CI / test (pull_request) Successful in 8m32s
CI / lint (push) Successful in 2m30s
CI / test (push) Successful in 8m36s
CI / frontend (push) Successful in 2m13s
CI / reuse (push) Successful in 4s
A layout seeds the collapsible sections; it should not hold them there. applyLayout writes the disclosure state of the advanced, audio and scheduler sections, and it runs far more often than a layout change: render() calls applyRigList() for every SSE frame carrying `remotes`, which calls setActiveRig() unconditionally, which re-applies the layout. An operator who expanded a section that the selected layout collapses by default therefore had it shut again within about a second, which read as the section being locked by the layout — most visibly the scheduler under Compact. Write the section state only when the layout actually changes, or the first time each section appears in the DOM, since the advanced controls are constructed after the first applyLayout call. Switching layout still reseeds every section, so choosing a layout keeps meaning "give me these defaults". Verified in Chromium: with Compact selected, activating the scheduler summary opens the section and it survives both a rig-state refresh and a repeated applyLayout, while selecting Full still reseeds it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
2f4973ed70 |
[chore](trx-rs): allow the sccache bind mount on the CI runner
CI / test (pull_request) Successful in 13m51s
CI / frontend (pull_request) Successful in 5m1s
CI / test (push) Successful in 7m43s
CI / frontend (push) Successful in 2m18s
CI / reuse (pull_request) Successful in 4s
CI / lint (pull_request) Successful in 4m22s
CI / lint (push) Successful in 2m23s
CI / reuse (push) Successful in 1m18s
act_runner validates every bind mount against `valid_volumes`, which defaults to an empty allowlist, so the `-v /var/cache/sccache:/sccache` in `container.options` was dropped on every job. The only trace is one line in the job log — "[/var/cache/sccache] is not a valid volume, will be ignored" — after which SCCACHE_DIR points at a path that does not outlive the container, so the shared compilation cache never persisted. Allow that one path rather than the `**` wildcard: the runner is the only thing mounting host directories here, and a narrow allowlist keeps a workflow from mounting arbitrary host paths into a job container. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
26b00608b2 |
[chore](trx-rs): force-pull the SDK image on the CI runner
The workflow references the SDK image by the moving `:latest` tag, and act_runner skips the pull when a local copy of that tag already exists: the job log reports `docker pull ... forcePull=false` followed by `Image exists? true`. Pushing a rebuilt image therefore changes nothing until someone pulls on the VM by hand, and the run fails as though the image never gained the tool that was added to the Containerfile — `sccache` resolving as "No such file or directory" while the pinned toolchain from an earlier build of the same tag resolves fine. Set `force_pull: true` so a pushed image is what actually runs, and document the manual refresh for runners configured before this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
c2455bb08c |
[chore](trx-rs): build the SDK image natively on x86_64 and arm64
The sccache release asset is per-architecture and the Containerfile hardcoded the x86_64 triple, so an arm64 build produced an image whose sccache binary could not execute. Everything else in the image — the Debian base, the build dependencies, Node.js and rustup — already resolves per architecture, so that one URL was what pinned the image to amd64 and forced Rosetta or qemu on Apple Silicon. Resolve the triple from `uname -m`, which reflects the build platform under plain docker/podman build as well as buildx, unlike the BuildKit-only TARGETARCH. Document publishing `:latest` as a manifest list built natively on a host of each architecture, since a single-architecture tag sends the other side back to emulation, and note that Apple's `container` CLI needs Rosetta for its BuildKit helper VM regardless of the target. Pick the act_runner download by architecture for the same reason. Verified on arm64: the case arm selects sccache-v0.8.2-aarch64-unknown-linux-musl, and the installed binary reports `sccache 0.8.2` running natively. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
22ff1349f3 |
[chore](trx-rs): run the frontend job in the SDK image
The frontend job was added while CI still targeted host-executor runners, so it never gained the `container:` key the lint and test jobs use. On the Docker executor it lands on a bare job container and fails the same way the Rust jobs did before this branch: `npm` is missing, the Chromium install shells out to `sudo apt-get`, and `npm run verify-generated` regenerates the Rust wire contracts, so it needs `cargo` too. Run it in the SDK image, which already ships Node.js, Chromium at the path the browser smoke test defaults to, and the pinned Rust toolchain. Installing Chromium per run is then redundant. Drop the job's trailing `reuse lint`. The SDK image deliberately carries nothing REUSE-related, and the separate `reuse` job lints the whole repository with the upstream action, generated assets included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
8b72573521 |
[chore](trx-rs): add sccache compilation cache
Bake sccache into the SDK image and enable it via RUSTC_WRAPPER in CI and the devcontainer (not repo-wide, so non-SDK builds are unaffected). - container/Containerfile: install the sccache musl binary. - ci.yml: RUSTC_WRAPPER=sccache, CARGO_INCREMENTAL=0, SCCACHE_DIR=/sccache, cache size cap, plus a `sccache --show-stats` step per job. - runner-config.example.yaml: bind-mount /var/cache/sccache into job containers so the cache persists across runs and is shared between jobs. - .devcontainer: enable sccache with a named cache volume. Assisted-By: Claude Code (claude-opus-4) Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
f64032dcbe |
[chore](trx-rs): add OpenRC service for act_runner on Alpine
The runner host is Alpine (OpenRC, no systemd). Add an OpenRC init script for act_runner (supervise-daemon, depends on docker) plus a conf.d example for running one instance per project, and rewrite the runner section of the README with Alpine setup steps (apk docker, dedicated user in the docker group, register, service install). Assisted-By: Claude Code (claude-opus-4) Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
8fd1761688 |
[chore](trx-rs): use nested SDK image path trx-rs/sdk
Match the image name that was pushed to the registry (git.haxx.space/sjg/trx-rs/sdk) across the workflow, devcontainer and README. Assisted-By: Claude Code (claude-opus-4) Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
7022f20b76 |
[chore](trx-rs): shared SDK image for CI and developers
Rework container/ from a host-executor act_runner image into a single "SDK" build image used everywhere: as the CI job container (Docker executor) and by developers locally / via .devcontainer. It bakes in a pinned Rust toolchain and all build dependencies, so CI and every developer share the exact same rustc/clippy. - container/Containerfile: SDK image (Debian + deps + pinned Rust + Node). - rust-toolchain.toml: pin the toolchain to match the image; also ends the "CI clippy newer than local" version skew. - .gitea/workflows/ci.yml: lint/test run inside the SDK image via `container:`; reuse returns to fsfe/reuse-action (Docker executor runs it as a sibling container, so nothing REUSE-related is baked in). - .devcontainer/devcontainer.json: dev use of the same image. - container/runner-config.example.yaml: Docker-executor runner config for the CI VM, capped for a 2-thread budget. - Drop the obsolete host-executor entrypoint/config/Quadlet units. Assisted-By: Claude Code (claude-opus-4) Claude-Session: https://claude.ai/code/session_01NFpGtGTWUEYXLwZeZs2RAV Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
0d4c657b97 |
[fix](trx-frontend-http): route feature bundles through the host contract
CI / lint (pull_request) Failing after 2s
CI / test (pull_request) Failing after 1s
CI / frontend (pull_request) Failing after 41s
CI / reuse (pull_request) Failing after 2s
CI / lint (push) Failing after 2s
CI / test (push) Failing after 2s
CI / frontend (push) Failing after 36s
CI / reuse (push) Failing after 1s
The bookmark fix addressed one instance of a defect the TypeScript migration left across the feature entries. app.js stopped being a classic script, so its top-level declarations are no longer shared globals, but the converted entries kept reading them as window properties that nothing publishes. Restore the broken behavior: - ais, aprs, hf-aprs read serverLat, serverLon and haversineKm as undefined, so every positioned packet rendered an empty distance. - ais, aprs, hf-aprs, cw, sat, vdes, wefax, wspr called an undefined postPath, so clear-history and decoder toggles threw. - scheduler read authRole as undefined, so the lazy-load path never self-initialized and the Settings tab opened an inert scheduler. - background-decode read authEnabled as undefined, so control gating fell back to role-only. - vchan read fifteen application values and services as undefined: mode and bandwidth sync, the out-of-band hint, RX audio restart, and the frequency field all silently no-opped on a virtual channel. - vchan wrapped window.refreshFreqDisplay, capturing an undefined original exactly as it did for setRigFrequency, so leaving a channel never restored the application's own frequency display. - _audioChannelOverride was a const that nothing could assign, so RX audio always subscribed to the primary channel. - ftx-family read fmtTime, a helper legacy ft8.js owned locally, so decode bar timestamps rendered empty. Declare the contract once in plugins/host.ts and import it from the feature entries, rather than restoring globals that docs/frontend-architecture.md excludes. trx.state gains jogUnit, rxActive and audioChannelOverride, and makes lastModeName writable; trx.core gains the tuning, RDS, WFM, jog and RX audio services the entries need. vchan interception moves to an interceptFreqDisplay service method that refreshFreqDisplay calls, matching the frequency, mode and bandwidth interception it already registers. Reading registry-built elements through a strict lookup is the same defect as in bookmarks: renderTimelineNeedle guards its result, but schedulerEl throws, so the now-initializing scheduler crashed on the timeline needle group that its own SVG creates. Feature tests move onto a shared host fixture, and entries that now import a common module are bundled through bundleEntry like the other shared-module entries. Covers scheduler self-initialization and the distance path that the bare window reads broke. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
23dbcac5b6 |
[fix](trx-frontend-http): restore bookmark host contract
The TypeScript migration turned app.js from a classic script into an ES module, so its top-level declarations stopped being shared globals. bookmarks.ts was converted verbatim and kept reading them as window properties, which app.ts no longer publishes. Every bookmark interaction read undefined: the Add Bookmark and Select All buttons stayed hidden because the auth check saw no authEnabled or authRole, per-rig scopes were missing from the scope picker and the move target, decoder checkboxes were never built, and Tune threw on bridge.postPath before issuing a single request. Extend the typed window.trx host contract instead of restoring globals, as docs/frontend-architecture.md closes the standalone window property list. trx.state publishes authEnabled; trx.core publishes setRigFrequency, applyLocalTunedFrequency, armOptimisticFrequency, syncBandwidthInput, scheduleSpectrumDraw, and onDecoderRegistryReady. Replace the vchan setRigFrequency wrapper with an interceptFrequency service method, matching interceptMode and interceptBandwidth. The wrapper captured an undefined original and silently dropped every tune; routing interception through setRigFrequency also restores virtual channel redirection for the application's own tuning. Read registry-built elements through bmOptionalEl, since bmEl throws and the decoder checkboxes and decode toggle buttons are legitimately absent until the registry arrives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space> |
||
|
|
695434942f |
chore: complete TypeScript migration cleanup
CI / lint (pull_request) Failing after 2s
CI / test (pull_request) Failing after 2s
CI / frontend (pull_request) Failing after 42s
CI / reuse (pull_request) Failing after 2s
CI / lint (push) Failing after 2s
CI / test (push) Failing after 1s
CI / frontend (push) Failing after 44s
CI / reuse (push) Failing after 2s
|