-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathblog.html
More file actions
2452 lines (2037 loc) · 212 KB
/
Copy pathblog.html
File metadata and controls
2452 lines (2037 loc) · 212 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Blog — AetherSDR</title>
<meta name="description" content="Release notes, engineering deep-dives, and station notes from the AetherSDR project — the native amateur-radio workstation for FlexRadio transceivers." />
<link rel="canonical" href="https://www.aethersdr.com/blog" />
<meta name="theme-color" content="#060b13" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="AetherSDR" />
<meta property="og:url" content="https://www.aethersdr.com/blog" />
<meta property="og:title" content="AetherSDR Blog" />
<meta property="og:description" content="Release notes, engineering deep-dives, and station notes from the AetherSDR project." />
<meta property="og:image" content="https://www.aethersdr.com/assets/img/og-image.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="AetherSDR's 3D stacked-trace panadapter and waterfall over the 40m band, with the full control surface." />
<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="AetherSDR Blog" />
<meta name="twitter:description" content="Release notes, engineering deep-dives, and station notes from the AetherSDR project." />
<meta name="twitter:image" content="https://www.aethersdr.com/assets/img/og-image.jpg" />
<link rel="icon" type="image/png" href="assets/img/logo-96.png" />
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon.png" />
<link rel="stylesheet" href="styles.css?v=8" />
<!-- Without JS there's nothing to route, so show every post stacked under
the index rather than a grid of cards that go nowhere. -->
<noscript>
<style>
.blog-post[hidden] { display: block; }
.blog-back { display: none; }
</style>
</noscript>
</head>
<body>
<!-- ===================== NAV ===================== -->
<header class="nav">
<div class="wrap nav-inner">
<a class="brand" href="index.html">
<img src="assets/img/logo-96.png" alt="AetherSDR logo" />
<span>Aether<b>SDR</b></span>
</a>
<nav class="nav-links">
<a href="index.html#features">Features</a>
<a href="index.html#built">How it's built</a>
<a href="lineage.html">Lineage</a>
<a href="index.html#partners">Partners</a>
<a href="index.html#contribute">Contribute</a>
<a href="index.html#sponsor">Sponsor</a>
<div class="nav-item">
<button class="nav-dd-toggle" type="button" aria-haspopup="true">
Support
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="nav-dd" role="menu">
<a role="menuitem" href="https://lu5dx.github.io/AetherSDRDocsEnglish/latest/" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/><path d="M8 10h8M8 14h8"/></svg>
User Manual
</a>
<a role="menuitem" href="https://www.youtube.com/@AetherSDR" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M10 9l5 3-5 3z"/></svg>
Video tutorials
</a>
<a role="menuitem" href="https://github.com/aethersdr/AetherSDR/issues/new/choose" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/></svg>
File an issue on GitHub
</a>
</div>
</div>
<a href="blog.html" class="nav-keep is-active" aria-current="page">Blog</a>
</nav>
<div class="nav-cta">
<a class="btn btn-primary btn-sm" href="index.html#download">Download</a>
</div>
</div>
</header>
<a id="top"></a>
<main id="blog-main">
<!-- ===================== BLOG INDEX ===================== -->
<!-- Card grid. Each card links to #<slug>, matched to an <article data-post="<slug>">
below by assets/js/blog.js. Add a post = add one card here + one article there. -->
<section class="block blog-index" id="blog-index">
<div class="wrap">
<div class="section-head">
<span class="eyebrow"><span class="dot"></span> From the project</span>
<h2>The AetherSDR blog</h2>
<p>Release notes, engineering deep-dives, and notes from the bench — how the
workstation gets built, and what's landing next.</p>
</div>
<div class="blog-grid">
<!-- PINNED: stays at the top regardless of date. New posts go BELOW
this card, above the newest release post. -->
<a class="blog-card is-pinned" href="#first-contribution">
<div class="blog-card-media">
<!-- Topmost card is above the fold: eager, so it isn't the LCP holdup. -->
<img width="640" height="560" src="assets/img/first-contribution-card.svg" alt="A contribution pipeline drawn as a signal chain — issue, fix, merged — over a spectrum trace" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<span class="blog-pin">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.8V4h6v6.8l2.5 3.2h-11z"/></svg>
Pinned
</span>
<span class="sep" aria-hidden="true">·</span>
<time datetime="2026-07-26">July 26, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>8 min read</span>
</span>
<h3>Your first contribution to AetherSDR</h3>
<p>You no longer need to be a C++ programmer to fix something in this
project. You need judgement, a radio, and the patience to check work —
the same things that make a good Elmer. Here's what an AI coding agent
changes about contributing, and what it very deliberately doesn't.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-9-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-9-1-card.svg" alt="v26.9.1 release art — the map goes round, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-29">August 29, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>4 min read</span>
</span>
<h3>v26.9.1: a globe, a rotator, and buttons that tell the truth</h3>
<p>PSK Reporter gains an optional globe projection, Green Heron antenna and rotator control arrives as a native applet, and — quieter but more consequential — the interface stops offering you controls your radio doesn’t actually have.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-8-4">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-8-4-card.svg" alt="v26.8.4 release art — evidence, not assumption, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-22">August 22, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>4 min read</span>
</span>
<h3>v26.8.4: capabilities the radio has to prove</h3>
<p>Icom support stops assuming and starts checking. Each model is described by independently attested facets, and a radio nobody has profiled hides unsupported controls rather than borrowing another model’s commands. Plus client-timed CW on the Hermes-Lite 2.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-8-3">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-8-3-card.svg" alt="v26.8.3 release art — the workspace canvas, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-16">August 16, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>5 min read</span>
</span>
<h3>v26.8.3: a new shell, and the reason it’s off by default</h3>
<p>Workspace Canvas turns pans and applets into freely placed, layered items that can span several windows, with named workspaces bound to radio profiles. An install that never enables it never even gains a settings key. Plus an ordered command plane for the Icom.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-8-2">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-8-2-card.svg" alt="v26.8.2 release art — two more radios, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-09">August 9, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>14 min read</span>
</span>
<h3>v26.8.2: two more radios, and what “experimental” has to mean</h3>
<p>The Hermes-Lite 2 and the Icom IC-705 arrive on the vendor-neutral backend seam, labelled experimental — which here is a request for reports rather than a disclaimer. Plus SPE Expert amplifiers, a noise reducer found to be delivering a fifth of the attenuation it advertised, and a CW keyer measured at 26 WPM with 30 on the dial.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#one-main-branch">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/one-main-branch-card.svg" alt="A play drawn on a chalkboard: a formation runs routes at one line of scrimmage while the dashed flight of the ball arrives at a point marked main" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-06">August 6, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>24 min read</span>
</span>
<h3>An army of agents vs one main branch</h3>
<p>An essay on how a project with no human-authored code actually gets built. There's no ceiling on the number of AI agents proposing changes to it — at least one per contributor, and a single run can spawn ninety. What stops that becoming an unreviewable mess isn't better prompting: it's written-down authority, and a gate that doesn't care how confident anyone sounds.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-8-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-8-1-card.svg" alt="v26.8.1 release art — settings that survive, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-08-02">August 2, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>10 min read</span>
</span>
<h3>v26.8.1: settings that survive, and a way in when they don’t</h3>
<p>Your settings stop being an XML file and become a database — transactional saves, verified backups, and credentials moved out to your OS keychain. Then the part that matters when it goes wrong: a config editor that runs without the GUI, because the escape hatch can’t depend on the thing it’s rescuing.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-7-4">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-7-4-card.svg" alt="v26.7.4 release art — speech to text, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-07-26">July 26, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>10 min read</span>
</span>
<h3>v26.7.4: Copy Assist, and the eight milliseconds that ate half the audio</h3>
<p>AetherSDR now transcribes received voice on your own machine — whisper.cpp under the waterfall, confidence colour-coding, no audio leaving the computer unless you send it. Then the deep end: why it sounded broken with NR2 engaged, and how a repaint optimisation was quietly discarding half the speech.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-7-3">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-7-3-card.svg" alt="v26.7.3 release art — meters you can trust, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-07-19">July 19, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>5 min read</span>
</span>
<h3>v26.7.3: the cross-needle returns</h3>
<p>A cross-needle PWR/SWR applet brings the classic forward-power, reflected-power, and SWR view into AetherSDR — the meter where the reading is the crossing point. The analog S-meter gains configurable face themes, and the SWR response and scale maths got more accurate underneath.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-7-2">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-7-2-card.svg" alt="v26.7.2 release art — digital voice, drawn as an AetherSDR spectrum panel" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-07-12">July 12, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>7 min read</span>
</span>
<h3>v26.7.2: D-STAR, locally, with a dongle</h3>
<p>Complete local D-STAR through a connected ThumbDV hardware vocoder — receive and transmit digital voice, headers, callsigns, repeater routing, and 20-character messages. Software AMBE is deliberately not included, and the reason is worth explaining. Plus an MCP server and Blackwell GPU support for BNR.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-7-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-7-1-card.svg" alt="v26.7.1 release art — the band, over time, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-07-02">July 2, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>7 min read</span>
</span>
<h3>v26.7.1: the spectrum stands up</h3>
<p>A 2D/3D toggle turns the panadapter into a perspective stacked-trace surface — a rolling history of FFT traces receding into the distance, so you can watch a band's activity build rather than only seeing the live sweep. Ridge height is anchored to the measured noise floor, and the whole path runs at 60 fps.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-6-5">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-6-5-card.svg" alt="v26.6.5 release art — diversity across geography, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-06-28">June 28, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.6.5: aligning a Flex with a receiver a thousand miles away</h3>
<p>KiwiSDR receive sync uses GCC-PHAT correlation to time-align your Flex with a public Kiwi in both audio and the waterfall, so a diversity pair separated by a continent stays phase-coherent. Plus SmartMTR transmit meters and a profile-switcher applet.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-6-4">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-6-4-card.svg" alt="v26.6.4 release art — receivers worldwide, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-06-23">June 23, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.6.4: listening from somewhere else</h3>
<p>A KiwiSDR public-receiver browser lands — find and connect to public receivers worldwide, entirely independent of the FlexRadio path. It's deliberately policy-aware rather than pretending the world's volunteer-run receivers are a free API, and it physically cannot transmit.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-6-3">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-6-3-card.svg" alt="v26.6.3 release art — satellite data, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-06-14">June 14, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.6.3: WFM, and 9600-baud satellite work</h3>
<p>A per-slice WFM demodulator built specifically for G3RUH 9600-baud satellite telemetry — DAX IQ through Doppler correction, exact resampling, and a flat discriminator into a virtual audio cable. Plus an APRS client and a PSK Reporter map on a new reusable mapping engine.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-6-2">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-6-2-card.svg" alt="v26.6.2 release art — a complete packet station, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-06-08">June 8, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>7 min read</span>
</span>
<h3>v26.6.2: your radio is now the TNC</h3>
<p>AetherModem grows from a decoder into a full packet station — a KISS-over-TCP TNC server for any host app, a connected-mode AX.25 terminal that calls a real BBS, and a personal mailbox that answers. All over the built-in 1200-baud AFSK modem, with no external TNC hardware anywhere in the chain.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-6-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-6-1-card.svg" alt="v26.6.1 release art — hands on the radio, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-06-01">June 1, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.6.1: knobs, dials, and physical controls</h3>
<p>Three new classes of physical controller land — the Elgato Stream Deck+, the Ulanzi Dial, and native Icom RC-28 support — all opt-in by design so macOS never prompts for Input Monitoring unless you actually enable one. Plus a seven-year-old noise-reduction crackle finally traced to a Bessel function.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-5-3">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-5-3-card.svg" alt="v26.5.3 release art — aetherial tx complete, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-05-24">May 24, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>7 min read</span>
</span>
<h3>v26.5.3: the de-esser that was costing you 30 watts</h3>
<p>The Aetherial transmit path reaches feature-complete with a PAPR processor and a split-band de-esser. The de-esser rewrite is the interesting one: the old broadband implementation was quietly pulling roughly 30 W of forward power out of voice content, and the fix is a single change in how the gain is applied.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-5-2-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-5-2-1-card.svg" alt="v26.5.2.1 release art — aethermodem, phase 0, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-05-17">May 17, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.5.2.1: a packet decoder of our own</h3>
<p>AetherModem lands as a native HDLC/AX.25 receiver — a 21-lane phase bank, FCS validation, and a dedicated Packet Decode dialog, with RX live on 300-baud HF and 1200-baud VHF. Also the story of why this release has a fourth version digit, which turns out to be about one string in a device-identity handshake.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
<a class="blog-card" href="#release-26-5-1">
<div class="blog-card-media">
<img width="640" height="560" src="assets/img/release-26-5-1-card.svg" alt="v26.5.1 release art — the 1.0 release, drawn as an AetherSDR spectrum panel" loading="lazy" />
</div>
<div class="blog-card-body">
<span class="blog-meta">
<time datetime="2026-05-10">May 10, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>6 min read</span>
</span>
<h3>v26.5.1: dropping the pre-1.0 framing</h3>
<p>After eight 0.9.x cycles the version number had stopped telling the truth. v26.5.1 is the 1.0-equivalent release — full SmartSDR feature coverage, the Aetherial strip complete on both paths, and native builds on three platforms. It also switches the project to CalVer, for a reason worth explaining.</p>
<span class="blog-more">Read post
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
</span>
</div>
</a>
</div>
</div>
</section>
<!-- ===================== POSTS ===================== -->
<!-- Each article is hidden until its slug is routed to. Keep data-post in sync
with the card href above. Body copy uses .blog-body for prose styling. -->
<article class="blog-post" data-post="first-contribution" tabindex="-1" aria-labelledby="h-first-contribution" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/first-contribution-hero.svg" alt="The contribution pipeline drawn as an AetherSDR signal chain: issue, claim, fix, evidence, merged — over a spectrum trace and waterfall" />
</figure>
<span class="blog-meta">
<time datetime="2026-07-26">July 26, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>8 min read</span>
</span>
<h1 id="h-first-contribution">Your first contribution to AetherSDR</h1>
<div class="blog-body">
<p class="lede">You no longer need to be a C++ programmer to fix something
in this project. You need judgement, a radio, and the patience to check
work — the same things that make a good Elmer.</p>
<p>Every open-source project says it welcomes newcomers. Most then hand you
a wall of build instructions, a toolchain that fights you for a weekend,
and a code review that assumes you already know what a rebase is. The
welcome is sincere; the on-ramp is still a cliff.</p>
<p>That cliff is what actually changed. Not the code — the on-ramp. We've
published a <a href="https://github.com/aethersdr/AetherSDR/blob/main/docs/first-contribution-cheatsheet.md" target="_blank" rel="noopener">first-contribution cheat sheet</a>
that takes you from "I've never used GitHub" to a merged pull request, and
the striking thing about it is that you never type a command. Every step is
a sentence you say, in English, to an AI coding agent working inside your
editor.</p>
<h2>You're the control operator</h2>
<p>Here's the part worth being clear about, because it's where the whole
thing succeeds or fails: the agent does the typing, not the deciding.</p>
<p>It will install dependencies, find the bug, write the fix, and draft the
pull request. It will also sound completely confident while being wrong.
That is not a flaw you can prompt your way out of — it's the nature of the
tool, and it's exactly why the human in the chair still matters. Your job
is to read what it proposes before you say yes.</p>
<p>If you've ever supervised a Technician on your license, the arrangement
will feel familiar. Someone else is on the mic. You're still responsible
for what goes out. The cheat sheet states it as four rules, and the third
one — <em>it sounds confident even when wrong</em> — is the one to
internalize.</p>
<p>So the skill that makes you useful here isn't C++. It's knowing what
AetherSDR is supposed to do when you key up, and noticing when it doesn't.
That's operator knowledge, and no amount of model capability substitutes
for it.</p>
<h2>What it costs to start</h2>
<p>Three things: a free editor, a GitHub account, and one AI coding agent.
The cheat sheet walks through Copilot, Codex, and Claude Code side by side,
including which tiers can actually drive your editor and which are chat
only — a distinction worth checking before you pay for anything. There is
a genuinely free path. Limits shift often enough that the cheat sheet links
to the live pricing pages rather than quoting numbers that go stale.</p>
<p>The GitHub account is the one step nothing can do for you. Pick your
callsign as the username — it's public, and it's the handle the rest of us
will know you by.</p>
<h2>Finding a first job</h2>
<p>Issues tagged <a href="https://github.com/aethersdr/AetherSDR/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22" target="_blank" rel="noopener">good first issue</a>
are the front door. Ask your agent to read the open ones and explain the
top few in plain English, then to recommend one — it's surprisingly good at
judging which bugs are self-contained.</p>
<p>Then claim it before you start. Claiming means adding yourself to the
issue's assignees, not leaving a comment saying you'll take it. Our triage
bot is auto-assigned to everything, so an issue showing only the bot is
free. This is the one piece of etiquette that matters on day one: it's what
stops two people quietly fixing the same bug on a Saturday afternoon.</p>
<h2>Bring evidence, not vibes</h2>
<p>"Works on my machine" is not evidence, and this is where a first PR
usually either lands or stalls.</p>
<p>AetherSDR ships an agent automation bridge — a switch you flip at launch
that lets your agent drive the running application directly: read the state
of any control, move sliders, click buttons, capture the panadapter. So
instead of asserting the bug is fixed, your agent can walk the issue's
exact reproduction steps through the app and show the control values before
and after.</p>
<p>It's the difference between eyeballing the S-meter and putting the radio
on a service monitor. Reviewers here look for that trace, and a first pull
request that arrives with one moves noticeably faster than one that says
"works for me."</p>
<p>Two ground rules the cheat sheet is firm about, both of which protect your
station rather than the codebase: check the radio isn't already in use
before launching a test run, because two clients fighting over one radio
produce evidence that is simply wrong. And ask for receive-only testing.
The bridge refuses to transmit unless you explicitly enable it — leave it
that way for your first run.</p>
<h2>When it goes wrong</h2>
<p>It will. A build will fail, a check will go red, GitHub will call your
commit "Unverified" for reasons that make sense to roughly nobody the first
time.</p>
<p>The entire troubleshooting procedure is: copy whatever you see, paste it
to your agent, and add <em>"This happened. What does it mean and what
should we do?"</em> That sentence resolves the overwhelming majority of
first-timer problems, because the errors are well-known ones and the agent
has seen them all.</p>
<h2>Why we bother</h2>
<p>Ham radio has always run on people explaining things to each other —
someone signed your license test, someone talked you through your first
HF contact, someone told you your audio was too wide. That tradition is
the reason this project is worth contributing to at all.</p>
<p>The tooling now handles the part that used to gate people out: the syntax,
the build system, the git incantations. What it can't supply is someone who
knows what a properly-behaving panadapter looks like at 3 AM during a
contest. That's you.</p>
<p><a href="https://github.com/aethersdr/AetherSDR/blob/main/docs/first-contribution-cheatsheet.md" target="_blank" rel="noopener">Read the cheat sheet</a>,
pick an issue, and open the pull request. If you get stuck at any point,
<a href="https://github.com/aethersdr/AetherSDR/discussions" target="_blank" rel="noopener">start a discussion</a> —
asking early is not an imposition here, it's the tradition. 73.</p>
</div>
<a class="blog-back blog-back-end" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
</div>
</article>
<article class="blog-post" data-post="release-26-9-1" tabindex="-1" aria-labelledby="h-release-26-9-1" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/release-26-9-1-hero.svg" alt="v26.9.1 release art — the map goes round, drawn as an AetherSDR spectrum panel" />
</figure>
<span class="blog-meta">
<time datetime="2026-08-29">August 29, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>4 min read</span>
</span>
<h1 id="h-release-26-9-1">v26.9.1: a globe, a rotator, and buttons that tell the truth</h1>
<div class="blog-body">
<p class="lede">A globe projection for PSK Reporter, native antenna and rotator control, and — quieter, but the thread running under the whole release — an interface that stops offering you controls your radio doesn’t have.</p>
<h2>The map goes round</h2>
<p>PSK Reporter can now switch from the flat map to a globe. It is optional; the flat map is still there and still the default.</p>
<p>It earns its place because the paths an HF operator cares about are exactly the ones a rectangular projection treats worst. A great-circle path from the US to VK is a long, unintuitive curve on a flat map and a straight line on a sphere. When you are trying to work out whether the grey line is doing what you think it is doing, the projection that matches the physics is worth having.</p>
<h2>Something to point the antenna with</h2>
<p>A native <strong>Green Heron Everyware</strong> applet controls compatible antenna switches and rotators, so the thing that points your antenna now lives in the same window as the thing showing you where to point it.</p>
<h2>Controls that admit what they can’t do</h2>
<p>This is the part with no screenshot, and it is the most consequential thing in the release.</p>
<p>Flex-only controls now dim on non-Flex backends. A missing command plane fails visibly instead of silently. Automation verbs that only FlexRadio implements refuse an unsupported backend rather than reporting success. Radio sharing is single-sourced, and Demo mode reaches its backend through the capability extension namespace instead of a special case.</p>
<p>None of that is a feature you can point at. It is the difference between an interface that describes <em>your</em> radio and one that describes the radio we happened to develop against. And the failure it removes is the worst kind available: a control that appears to work, returns success, and changes nothing. A greyed-out button teaches you something true in about a second. A button that lies costs you an evening.</p>
<p>This is what supporting more than one family of radio actually demands. The features are the easy half.</p>
<h2>FlexRadio repairs</h2>
<p>Band shortcuts restore band-stack state again. PA temperature is back in the status bar on radios that report it. TNF removal now removes the notch instead of recreating it. And TCI <code>cw_macros</code> messages no longer append the receiver index to your macro text, which had been quietly editing what you sent.</p>
<h2>Also in this release</h2>
<p><strong>Seeing inside the app.</strong> System Info gains Threads and Logs tabs, so runtime detail is visible while you are looking at the problem rather than only afterwards in a support bundle. RTTY decoder sensitivity is adjustable from the decoder bar.</p>
<p><strong>The panadapter.</strong> Button-rail collapse state persists, the active band is highlighted in the spectrum overlay, and connect-time enumeration no longer overwrites the radio’s active slice.</p>
<p><strong>CW.</strong> Three fixes that all sit in the sidetone path: an explicit PortAudio sidetone selection is no longer captured by a shorter device-name match, Zero Beat mirrors correctly for CWL, and iambic keyer edges no longer echo back into the sidetone gate.</p>
<p><strong>Networked Icom</strong> continues along the evidence-gated line the last release set out. Read-only IC-9700 memories, synchronised dial lock, capability-gated DTCS on the IC-705 and IC-9700, IC-9700 CTCSS on transmit and receive, and model-gated extended repeater readback all follow each model’s attested surface. A long list of telemetry now reaches the interface correctly — supply voltage, PA drain current, continuous compression, LAN MOD phone level, RF power scaling, RF gain and preamp presentation, declared bands — while the readouts a model cannot support stay hidden rather than showing a plausible zero. TUNE owns its carrier, SWR and ALC survive isolated minimum replies, and RX controls stay subscribed across a reconnect.</p>
<p><strong>Automation</strong> gains FM repeater controls, text-view inspection, momentary key events and combo-box popup actions. On the project side, every registered test now carries a default 300-second timeout, synthetic transport tests are separated by verification layer, and there is a repeatable issue-fit, governance and code-quality review workflow for pull requests.</p>
<p>53 merged changes from 12 contributors, including a first-time contributor this cycle. Full release notes and the complete commit list: <a href="https://github.com/aethersdr/AetherSDR/releases/tag/v26.9.1">v26.9.1 on GitHub</a>.</p>
</div>
<a class="blog-back blog-back-end" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
</div>
</article>
<article class="blog-post" data-post="release-26-8-4" tabindex="-1" aria-labelledby="h-release-26-8-4" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/release-26-8-4-hero.svg" alt="v26.8.4 release art — evidence, not assumption, drawn as an AetherSDR spectrum panel" />
</figure>
<span class="blog-meta">
<time datetime="2026-08-22">August 22, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>4 min read</span>
</span>
<h1 id="h-release-26-8-4">v26.8.4: capabilities the radio has to prove</h1>
<div class="blog-body">
<p class="lede">Icom support stops assuming and starts checking. That sounds like plumbing, and the consequence is the opposite of plumbing: an unprofiled radio now shows you less, on purpose.</p>
<h2>Fail closed</h2>
<p>The IC-705, IC-7300MK2 and IC-9700 gain typed capability profiles. Each model’s behaviour is described by independently attested facets rather than by address checks scattered through the code, and a radio that nobody has profiled hides unsupported controls and telemetry instead of borrowing another model’s commands or calibration.</p>
<p>Failing closed is the entire point, and it is worth being explicit about the alternative. Assuming an unknown radio behaves like one you do know produces an interface that looks complete and is quietly wrong — a meter reading against another model’s calibration, a command the radio ignores, a control that reads back a value nobody set. An empty space in the interface is honest about the gap. A populated space that lies is not, and it costs the operator far more to discover.</p>
<p>What the profiles unlock is everything the models now actually attest to: radio-authoritative FM repeater tone, duplex, offset and momentary XFC — read first, recalled in a radio-safe order, and always released on hide, deactivation, capability change or disconnect. Real IF-width and twin-PBT readback, with filter slot, width and shift kept separate and only model-supported transmit edges written. The radio’s own network name in the status bar, kept distinct from canonical model, hostname and callsign. The CI-V CW text keyer. WFM where the model advertises it. The 2 m and 70 cm decks admitted on the IC-705, and IC-9700 band power limits modelled explicitly rather than assumed.</p>
<p>There is also a bounded recovery path: a confirmed IC-9700 CI-V stall now gets serial-stream recovery before a full reconnect, for the case where the RS-BA1 transport itself is still alive.</p>
<h2>CW on the right clock</h2>
<p>The Hermes-Lite 2 gains client-timed CW transmit, with host-scheduled key edges driving Protocol 1 CW and client-owned CW operating state that survives a restart.</p>
<p>Alongside it, scheduled key instants now reach the sidetone, the trace and netcw consistently, and a late edge shifts the sidetone anchor forward instead of being clamped. That last one is the difference between a keyer that quietly drifts away from you and one that catches back up — the same class of problem as the accumulating deadline in the iambic timing fix two releases ago, in a different part of the path.</p>
<p>Two smaller ones in the same area: PortAudio’s real default-device path is reachable for the default sidetone selection, and CWX macro rows stay readable at minimum window height.</p>
<h2>Also in this release</h2>
<p><strong>PSK Reporter</strong> renders more stations and paths for less per-item cost, through batched marker and path rendering and wrapped-map geometry, and gains smooth trackpad pinch-to-zoom. DIGL is no longer misclassified as RTTY, which restores the normal carrier marker and removes mark and space cues that meant nothing there.</p>
<p><strong>Audio.</strong> NR2 recovers from residual noise when AGC-T changes. Microphone capture stays float32 through the 48 kHz voice strip. Transmit-accumulator clears are serialised on the audio engine’s own thread, and RN2 frame accumulation is unified.</p>
<p><strong>Controllers.</strong> MIDI profiles round-trip safely: dotted profile names and paths survive, the app’s own Pitch Bend export re-imports, SmartSDR <code>.map</code> files translate direct band and mode selection, and Save reports its real outcome rather than a cheerful default. On macOS, Ulanzi input no longer seizes unrelated trackpads. AetherControl gains a direct settings link for FlexControl configuration.</p>
<p><strong>TCI</strong> starts only after readiness, echoes start and stop correctly, and no longer double-broadcasts <code>vfo:</code> on channel 0.</p>
<p><strong>Diagnostics.</strong> Startup logs and support bundles now record CPU SIMD, RAM and GPU capability inventory — the kind of thing you only notice is missing when a report arrives without it. MCP token guidance keeps <code>AETHER_MCP_TOKEN</code> out of persistent configuration, and release signing archives the tag build rather than <code>main</code>.</p>
<p>48 commits across 14 contributors, with two first-time contributors this cycle. Full release notes and the complete commit list: <a href="https://github.com/aethersdr/AetherSDR/releases/tag/v26.8.4">v26.8.4 on GitHub</a>.</p>
</div>
<a class="blog-back blog-back-end" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
</div>
</article>
<article class="blog-post" data-post="release-26-8-3" tabindex="-1" aria-labelledby="h-release-26-8-3" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/release-26-8-3-hero.svg" alt="v26.8.3 release art — the workspace canvas, drawn as an AetherSDR spectrum panel" />
</figure>
<span class="blog-meta">
<time datetime="2026-08-16">August 16, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>5 min read</span>
</span>
<h1 id="h-release-26-8-3">v26.8.3: a new shell, and the reason it’s off by default</h1>
<div class="blog-body">
<p class="lede">AetherSDR grows a second way to arrange itself. The decision worth writing about isn’t the canvas — it’s that you have to go and turn it on.</p>
<h2>The workspace canvas</h2>
<p><strong>View ▸ Workspace Canvas</strong> turns pans and applets into freely placed, resizable, layered items on a canvas that can span several top-level windows. Named workspaces recall <em>which</em> applets are open as well as where they sit, and they can be bound to radio profiles, so recalling a profile brings its layout with it.</p>
<p>It is opt-in and off by default. If you never open that menu, this release changes nothing about how AetherSDR looks or behaves — and an install that never enables it never even gains a settings key. Enabling it migrates your current layout into a “Classic” workspace you can always reset back to, and disabling it restores the old shell exactly while keeping your placement for next time.</p>
<p>That restraint is the interesting part. A new shell is about the most invasive thing you can ship into a program people have already arranged to their own liking, and the arrangement is not decoration — it is where your hands go without looking. Shipping it as something that leaves no trace unless you accept it means the release is safe for everyone who did not ask for it, which is most people.</p>
<h2>The limitation we kept on purpose</h2>
<p>Items move between canvas windows through a right-click <strong>Move to ▸</strong> menu rather than by dragging across windows.</p>
<p>Dragging would be nicer. It is also a cross-top-level reparent, which is the crash lineage behind three separate issues in this project’s history, so for now it goes down one deliberate path that is known to be safe. Shipping the good nine-tenths of a feature with the dangerous corner left out beats shipping all of it plus a new way to lose your session.</p>
<h2>A command plane for the Icom</h2>
<p>Every meter read, control write, reconciliation poll and PTT transition on a networked Icom now goes through one ordered command plane, instead of independent timers calling the transport whenever they felt like it. Dispatches are paced into 25 ms slots with a single reply-bearing transaction in flight, prioritised emergency-unkey → operator → PTT → active meter → control → maintenance, with duplicate reads and rapid writes coalesced by semantic register.</p>
<p>The case that forced it is worth walking through. A pre-key poll asked the radio whether it was transmitting. The client then issued <code>ON</code>. The delayed <code>OFF</code> reply to that earlier question arrived <em>after</em> the newer intent — and was applied, stopping transmit audio before the radio’s own <code>ON</code> confirmation came back.</p>
<p>Nothing was wrong with either message. They simply landed out of order, and the older one won. Stale completions arriving after a newer intent are now rejected. This is Icom-only; Flex and HL2 command, meter and PTT behaviour is untouched.</p>
<p>The IC-7300MK2 control surface is also complete against live hardware, closing 18 operator-visible defects, and an Icom connected with the CI-V field left blank now asks the radio for its own address instead of assuming the IC-705’s — which had left every other model silently ignoring the traffic and coming up looking healthy with no frequency, no scope and no transmit.</p>
<h2>Also in this release</h2>
<p><strong>Hermes-Lite 2 gets a working NB button.</strong> The HL2 ships raw IQ and runs no firmware DSP, so it correctly reports “no radio-side DSP” — and the interface hid noise blanking alongside NR and ANF. That was the right conclusion about the <em>command</em> and the wrong one about the <em>control</em>: WDSP’s impulse blanker can run here, and on this radio the raw IQ ahead of the demodulator is the only place it can run at all, because an impulse is short in time and wide in frequency, and once an 8192-tap bandpass has smeared it there is no spike left to blank. CW also gets a real BFO — the panadapter had been drawing the passband skirt where a USB filter sits, which on this radio meant the receiver was listening 600 Hz away from where it transmits. AGC mode and threshold survive a restart. And your client’s level control works again: WSJT-X’s <code>Pwr</code> slider is a digital attenuator on the audio the client streams, not a rig command, and the host speech ALC had been running over the top of it.</p>
<p><strong>VK3AMP amplifiers</strong> — 600 W, 1000 W and 2000 W — join ACOM and SPE outside the radio seam, with TCP control, UDP telemetry, safety interlocks and calibration curves unit-tested against real capture values.</p>
<p><strong>Transmit audio moves to 48 kHz float end to end</strong>, with a single high-quality conversion to transport rate and dither immediately before the final quantization, replacing a chain that had been converting and truncating several times over. <strong>Eight DAX RX channels</strong> work on a FLEX-6700 — the path had been capped at four, so slices on DAX 5–8 were carrying silence-fill from an audio device that existed while no radio-side stream was ever requested. And a CAT band change now brings the panadapter with it.</p>
<p><strong>Two crashes closed.</strong> A multi-hour Windows transmit crash resolved to a write 30,976 bytes past the signed 2 GiB boundary: fresh TCI audio suppressed the local microphone callback, so the capture device was never drained, and when local processing resumed the whole multi-gigabyte residue came back in a single read. Separately, a panadapter pop-out crash no longer becomes a boot loop — the pan ID was committed to the floating list <em>before</em> the reparent that could crash, and the restore path replayed that list on every connect, so one crash perpetuated itself with no in-app escape.</p>
<p><strong>Every TCI argument now goes through a checked parse.</strong> Malformed input did not fail — Qt returns <code>0</code>, so it became a valid-looking zero and the command proceeded. This class does not fail quietly, it succeeds convincingly: the command applies, a well-formed notification goes out, and neither the sender nor a second client watching the wire can tell. Thirty of the fifty sites were the receiver index, which resolves positionally, so <code>mute:abc,true</code> muted slice 0 and broadcast a notification naming it.</p>
<p>53 commits across 14 contributors. Full release notes and the complete commit list: <a href="https://github.com/aethersdr/AetherSDR/releases/tag/v26.8.3">v26.8.3 on GitHub</a>.</p>
</div>
<a class="blog-back blog-back-end" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
</div>
</article>
<article class="blog-post" data-post="release-26-8-2" tabindex="-1" aria-labelledby="h-release-26-8-2" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/release-26-8-2-hero.svg" alt="v26.8.2 release art — two more radios, drawn as an AetherSDR spectrum panel" />
</figure>
<span class="blog-meta">
<time datetime="2026-08-09">August 9, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>14 min read</span>
</span>
<h1 id="h-release-26-8-2">v26.8.2: two more radios, and what “experimental” has to mean</h1>
<div class="blog-body">
<p class="lede">AetherSDR now connects to two radios FlexRadio didn’t build. Both are labelled experimental, and that word is doing real work here — it is a statement about maturity and a request for reports, not a disclaimer.</p>
<h2>Two more families, on the same seam</h2>
<p>Everything above the radio in AetherSDR talks to an interface called <code>IRadioBackend</code> rather than to a radio. That seam has been in place for a while, and this is what it is for. This release puts two more implementations behind it: the <strong>Hermes-Lite 2</strong> and the <strong>Icom IC-705</strong>.</p>
<p>Both are experimental, and we would rather over-explain that than let you infer it. Neither is a supported radio family. FlexRadio remains the supported target. <a href="https://github.com/aethersdr/AetherSDR/blob/v26.8.2/README.md#supported-hardware">Supported Hardware</a> lists what each backend does and does not do today, and it is the honest list rather than the aspirational one.</p>
<p>What experimental does <em>not</em> mean here is “don’t tell us.” Bug reports and feature requests against both backends are open and wanted. The one thing to include is which radio and which firmware version, because at this stage the difference between “the backend is wrong” and “this firmware behaves differently” is the entire question. Reports from operators are the mechanism by which either family stops being experimental. There isn’t a second mechanism.</p>
<h2>The conformance harness went first</h2>
<p><code>radiocert</code> is the project’s radio-conformance harness — the thing that asks a backend whether it actually honours the contract the seam describes. The IC-705 is the first radio brought up with it in hand rather than run against it afterwards, and <code>docs/CERTIFICATION.md</code> gains thirteen new lessons (1.19–1.31) out of that bring-up.</p>
<p>The ordering matters more than it sounds. A conformance suite written after the implementation tends to encode what the implementation already does. It tests the code rather than the contract, and it passes on the first run, which ought to be the tell. Running it during bring-up inverts that: every place the radio didn’t fit the seam surfaced as a failure while there was still a choice about which of the two was wrong. Some of those thirteen lessons are about the Icom. Some are about the seam.</p>
<p>There are new design notes for the SPE amplifier, the Icom CI-V backend and HL2 frequency calibration alongside them, and <code>HERMES.md</code> gains a section on WDSP channel-open cost and FFTW wisdom.</p>
<h2>SPE Expert amplifiers</h2>
<p><strong>1.3K-FA, 1.5K-FA and 2K-FA</strong>, over serial (115200 8N1) or ser2net TCP, raw or telnet — both transports verified on real hardware. This is the second peripheral amplifier after ACOM, and like ACOM it sits entirely outside the radio seam. An amplifier is not a radio, and modelling it as one would have been the easy mistake to make twice.</p>
<p>The applet carries power, antenna-SWR and ATU-SWR gauges, V/I/temperature readouts, warning and alarm banners, and the front-panel keystrokes. The power gauge rescales with the selected LOW/MID/HIGH level so that it matches the reading on the amplifier’s own display, which is the version of “correct” that matters when both are on the desk in front of you.</p>
<p>Two details are worth pulling out of the feature list. <strong>Poll silence is detected and reported</strong>, because a TCP session can stay perfectly healthy after the amplifier at the far end has been switched off — the socket is fine, the amplifier is not, and without something watching for silence the gauges go on showing the last thing the amp said. That is the metering-staleness problem from v26.8.1 wearing a network adapter. And the <strong>power-ON pulse works over the network</strong>: it is an RFC 2217 DTR/RTS pulse, so switching the amplifier on is not something you have to be in the room for.</p>
<h2>MNR was advertising an attenuation it wasn’t delivering</h2>
<p>This one changes what your existing settings do, so it is worth reading even if you never open the noise-reduction menu.</p>
<p>MNR’s minimum-statistics estimator was taking a raw minimum across 25 periodograms. The minimum of a noisy quantity is biased low by construction, so the noise estimate came out well under the actual noise floor, and the Wiener gain computed from it sat close to unity. A gain of one is “pass the signal through unchanged.” Measured maximum-strength stationary attenuation was <strong>4.54 dB</strong>, on a control whose top end was meant to be in the twenties.</p>
<p>It is now <strong>24.4 dB</strong>, scale-invariant within 1 dB, with a +18.8 dB SNR improvement on speech-like input for −2.8 dB of desired-signal level.</p>
<p><strong>Start lower than you are used to.</strong> Whatever number you settled on, you settled on it by ear against an MNR that was removing about a fifth of what it claimed. That setting now describes something else. Nothing in your configuration changed; what the configuration means did.</p>
<p>The redundant <code>Enable MNR</code> checkbox goes with it. The method selector is the sole enable now, consistent with every other AetherDSP method — two controls for one piece of state is a bug generator, because it lets the interface represent a condition the DSP cannot actually be in.</p>
<h2>The keyer was running slow, and the error never came back</h2>
<p>Measured on an Intel MacBook Pro with a HaliKey Serial, at a 30 WPM setting: <strong>45.70 ms per unit, where 30 WPM is 40 ms.</strong> That is a keyer sending 26.26 WPM with 30 on the dial.</p>
<p>The cause is one of the more instructive bugs in this release. <code>workerLoop()</code> armed each element’s deadline as <code>now() + duration</code>, and read <code>now()</code> <em>after</em> the previous wait returned. Every element therefore started from wherever the last one actually finished, inheriting its lateness and adding a fresh error of its own. Nothing in the loop ever referred back to when the squeeze began, so there was no reference against which accumulated error could be repaid. Each element was on time relative to the one before it, and progressively later relative to the operator.</p>
<p>Deadlines now advance on an absolute <code>steady_clock</code> grid anchored at squeeze start and tracked in nanoseconds, and key transitions are timestamped into a lock-free ring and mapped to exact sample offsets instead of being quantised to whichever audio block they landed in. Measured the same way afterwards: <strong>40.002 ms per unit — 29.998 WPM.</strong></p>
<p>Separately, <strong>Mode B no longer drops the trailing element on a clean simultaneous release.</strong> The memory latch ran only when the condition variable woke, and a wake caused by a release always observes post-release state — so the latch never saw the squeeze it existed to remember, and the keyer quietly behaved like Mode A. That was routine rather than rare on the serial path, where a 10 ms poll collapses both edges into a single event.</p>
<p>Thanks to @rsaue, whose Mode B root-cause analysis verified accurate in full, and to @williamscody for the timing logs that made both halves of the sidetone fix measurable rather than arguable.</p>
<h2>60m, as the FCC actually has it</h2>
<p>The US 60m band plan followed the ARRL’s, which still carried the pre-2026 five-channel allocation. The FCC’s Report & Order took effect on <strong>13 February 2026</strong>, and the panadapter had not noticed.</p>
<p>Two things were wrong on screen. A 100 W channel was being drawn over spectrum that is capped at 9.15 W ERP. And 15 kHz that US operators have had since February was not drawn at all.</p>
<p>The first is the one to sit with, because a band plan is not decoration. It is a legal display, and an operator glancing at it to check a power limit was being given the wrong answer confidently.</p>
<p>The new <strong>5351.5 – 5366.5 kHz</strong> segment is now drawn in its own colour, so the power boundary reads straight off the panadapter instead of out of a document. The retired 5358.5 kHz channel is gone. The four channels that survived keep their historic numbering — 1, 2, 4, 5, with the gap where 3 used to be. Renumbering them 1 through 4 would have been tidier, and would have silently disagreed with every log entry anyone has already written.</p>
<p>While in there: the 60m band edges were clipping the top 1.3 kHz of the 5405 kHz channel, so band-stack entries in that sliver saved under “Other” and digital-mode band matching failed.</p>
<p>Thanks to <strong>@csylvain (KB3CS)</strong> for catching that the triage on this was arguing from superseded law. That is the failure worth naming: the reasoning was sound, the source was two years stale, and nothing inside the argument would ever have revealed it. Somebody has to already know.</p>
<h2>Spots, schedules, and a second pan</h2>
<p>Three new overlays, and a small window that is more useful than its size suggests.</p>
<p><strong>N1MM+ and DXLog contest spots</strong> arrive over a SmartSDR-CAT-compatible UDP XML listener, with dupe, needed-multiplier, CQ, busy and bust status shown on the panadapter and eight configurable status-to-colour swatches. The default port is 12060, matching the <code>FocusHelperN1MMPort</code> SmartSDR documents, so an existing broadcast configuration works unchanged. Spots are keyed by callsign plus band rather than deduplicated by frequency, because N1MM is the only feed that explicitly says when to add, update and remove a spot — where the lifecycle is stated you can follow it, instead of inferring it from where things moved.</p>
<p><strong>The EiBi shortwave broadcast schedule</strong> gets its own SpotHub tab: auto-start, a live spot counter, forced re-download, conditional <code>If-Modified-Since</code> updates on a 7-day threshold, and a spot colour that repaints active markers as you change it. It resolves 289 country codes, 560 language and mode codes, target areas and 1,629 transmitter sites into the tooltips, so a carrier on 49m comes with a name attached.</p>
<p><strong>The KiwiSDR DX Community database</strong> is an independent, opt-in overlay for shortwave utility, beacon, broadcast and DX spots — cyan diamonds on the band-plan strip, tooltips carrying frequency, station, mode, passband cuts and notes, and click-to-tune that sets frequency, filter bandwidth and, with SpotHub’s <strong>Auto</strong> on, the mode. Mode strings pass through a normalising whitelist first, which is the boundary between “this community database has a typo in it” and “your radio just received a command nobody wrote.”</p>
<p><strong>The K4-style mini-pan</strong> is a detachable ±5/±10 kHz window centred on the active VFO, independent of the main panadapter and usable when the main pan is hidden in Minimal Mode. It is a genuine top-level window rather than a docked panel, so it floats over other applications and survives AetherSDR being minimised — which is the entire point when a contest logger owns the screen. Live on a FLEX-6500 it runs at about 26 Hz per bin, five times finer than the 200 kHz main pan.</p>
<p>Smaller, in the same area: the SpotHub <strong>Time column sorts</strong> at last — only Freq ever returned a sort value, so sorting by frequency had been a one-way trip. The FreeDV Reporter panel gains a status-message field that stays in sync with SpotHub → FreeDV. And <strong>KiwiSDR receiver lists import and export as CSV</strong>, merging on a normalised endpoint so re-importing the same file is idempotent; passwords are never exported, and stay in the OS keychain.</p>
<h2>The one file we were never shipping</h2>
<p><code>AetherSDR.exe</code> genuinely imports the MSVC OpenMP runtime, <code>vcomp140.dll</code>. Our packaging never shipped it. It resolved out of System32 on machines with the Visual C++ redistributable installed, which is nearly all of them — so it worked everywhere, on every Windows artifact, for every release since Copy Assist landed.</p>
<p>What that actually left was a single file’s worth of machine-wide dependency inside an otherwise self-contained payload. Any other installer that repaired, downgraded or removed the redistributable took AetherSDR down with it, and reinstalling AetherSDR could not fix it, because AetherSDR was never what put the file there. All three Windows artifacts were affected.</p>
<p>It now ships app-local. The more durable half of the fix is a CI gate, <code>check-deploy-dependencies.ps1</code>, which walks every PE in the payload and fails when an MSVC runtime import is not app-local — checking app-local <strong>before</strong> it probes System32. That ordering is the whole thing. Build machines have the redistributable installed. A check that asks “can this dependency resolve?” gets a yes from the one machine least qualified to answer, which is exactly how this shipped unnoticed release after release.</p>
<h2>Also in this release</h2>
<p><strong>Spectrum, waterfall and 3D.</strong> The 3D stacked-trace wedges close: every row covered the same frequency span while the far rows narrowed with perspective, leaving two empty black triangles flanking the surface. A <strong>3D Span</strong> slider now sits under Display → 3D VIEW, and 0 restores the existing rendering exactly. 3D FFT works on the Raspberry Pi 5 again — <code>dss_mesh</code> is the only shader using the <code>flat</code> qualifier, no 140 slice existed for it, and the Pi’s v3d driver fell back to a 120 slice the shader cannot compile against; desktop GPUs picked the 150 slice, which is why CI never saw it. History outlines render on Linux/OpenGL. Waterfall interpolation is clamped at history boundaries, ending the occasional bright, dark or scrambled horizontal line during a rapid pan or zoom, and Waterfall Blanker rows stay in a single frequency frame. A GPU that fails mid-session now says so on screen — <code>renderFailed()</code> was firing with nothing listening, leaving a blank panadapter and no explanation. Off-screen slice indicators centre on a single click, including the active one, which previously emitted nothing at all.</p>
<p><strong>Rendering on awkward platforms.</strong> Windows panadapters are no longer blank under software OpenGL: with <code>AETHER_NO_GPU</code> or <code>QT_OPENGL=software</code> the panadapter chose OpenGL while the hidden WAVE scope had already chosen D3D11 for the same top-level window, and Qt refuses to mix QRhi APIs in one backing store — 13,804 <code>QRhiWidget: No QRhi</code> messages, every panadapter transparent. The default D3D11 path is unchanged. A headless Wayland session now prefers xcb automatically, which is the VNC and wayvnc case: with no DRM scanout, every frame’s <code>makeCurrent</code> fails with <code>EGL_BAD_MATCH</code> and <code>AETHER_NO_GPU=1</code> does not help. Detection is deliberately tri-state, a <code>QT_QPA_PLATFORM</code> you set yourself still wins, and the decision is logged at startup so it is diagnosable from a support bundle. On Linux, the Connect to Radio dialog reopens reliably under xcb — it had been wedging in the ICCCM Withdrawn state under Mutter/XWayland with <code>isVisible()</code> cheerfully reporting true, leaving no way to disconnect.</p>
<p><strong>Audio and DSP.</strong> DFNR no longer pumps on stereo; it was driving a delayed dry stereo signal with a fast processed-to-dry power envelope, the same family of fault fixed in RN2 last release. The AetherVoice EQ canvas caches its layers instead of rebuilding the band response, grid, labels and band-plan decoration on the GUI thread every analyzer frame — on the same Windows VM, 73.1% to 37.4% of one core at 1280×900, and 82.8% to 43.1% at 2200×1300. Audio mute commands now reach the model: the three mute setters sent their command and returned without ever assigning or emitting, and <code>FlexBackend</code> is the only parser of those statuses, so on every other backend the flags sat at their <code>false</code> initialisers for the whole session — mute the headphones, nudge the volume, and the glyph flipped back. The title-bar headphone mute also reconciles with the radio, so a second Multi-Flex client changing it no longer leaves the icon lying.</p>
<p><strong>Copy Assist.</strong> A GPU that cannot be used now falls back to CPU instead of taking the application down with it. On a machine whose Vulkan stack enumerates GPUs but cannot create a logical device, Copy Assist was killing the process silently — and it is the <em>second</em> GPU attempt that does it, which no <code>try</code>/<code>catch</code> can intercept, so the only fix is to never make the second attempt. The selector now labels unusable devices, resolves to the first usable one or to CPU, and says in plain language when it has fallen back; previously the dialog went on naming a GPU that was running nothing while the queue grew from 7.6 to 22.5 seconds. Enabling speaker labels no longer freezes the GUI. And <strong>boundary overlap</strong> is new and opt-in: when an utterance is force-closed by the max-length cap the cut can land mid-word, and because each segment is decoded independently the straddling word is mangled or lost. A small window of trailing audio now carries into the next segment, at no inference cost. The slider starts at 0 ms.</p>
<p><strong>Controllers and TCI.</strong> The Ulanzi Dial gains a rotary action selector — frequency, filter bandwidth, slice and master volume, panadapter zoom, RIT/XIT, AGC-T, RF gain, APF, CW speed and RF power — and filter-preset stepping now scales with the magnitude of the rotary step instead of discarding it. MIDI bindings can be typed in by hand and edited per row, so one that Learn captured wrong can be corrected without re-learning; the settings format is untouched, so a file written by this build still loads in older ones. On TCI, a use-after-free in the DAX RX path is fixed, reproduced under ASan and triggered deterministically by a client switching from 48 kHz to native 24 kHz with sub-threshold samples still staged. Malformed input is now rejected rather than reinterpreted — an empty <code>volume:</code> argument parsed to <code>0.0</code>, and 0 dB is the <em>top</em> of that range, so a malformed command landed on the loudest value the interface can express; an unrecognised modulation name silently set USB while the notification echoed the client’s own wrong name back at it. And the <code>aether.cat</code> log category is relabelled <strong>“TCI / CAT / rigctld”</strong>, because operators chasing a TCI problem scanned the checkbox grid, found no mention of TCI, and reasonably concluded there was nothing to turn on.</p>
<p><strong>Interface.</strong> Choose Background Image gains a thumbnail view, with an Explorer-style small-icons toggle and a preview pane that grows with the dialog; decoding runs off the GUI thread behind the same decompression-bomb guard used for callsign photos. The analog power gauge picks the right scale at connect instead of coming up on 120 W and correcting after a band change. The FFTW wisdom popup stopped strobing — a deliberate infinite opacity animation past 90%, which supplied no elapsed time or status and left a slow process looking frozen; it is now a low-key activity indicator with a monotonic timer and a “still working” explanation after ten quiet seconds. The PGXL fan-mode dropdown fits its widest item, its hand-rolled stylesheet having set a 10px font on the combo but not on its popup, which is a separate top-level window and does not inherit it — root cause and fix worked out by the triage bot on the issue. And every model-lifetime callback is installed exactly once; two self-connections were owned by the <code>RadioModel</code> rather than by the replaceable backend, so every radio-family switch accumulated another capability publication and TX-power push.</p>
<p>Also: an undefined downcast that was aborting the sanitizer job before it printed a single result line is gone.</p>
<p>57 commits across 14 contributors. Full release notes and the complete commit list: <a href="https://github.com/aethersdr/AetherSDR/releases/tag/v26.8.2">v26.8.2 on GitHub</a>.</p>
</div>
<a class="blog-back blog-back-end" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
</div>
</article>
<article class="blog-post" data-post="one-main-branch" tabindex="-1" aria-labelledby="h-one-main-branch" hidden>
<div class="wrap blog-post-wrap">
<a class="blog-back" href="#" data-blog-back>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
Back to blog
</a>
<figure class="blog-hero">
<img width="1200" height="630" src="assets/img/one-main-branch-hero.svg" alt="A play drawn on a chalkboard: a full formation runs routes at one line of scrimmage, two of them stopped there, while the dashed flight of the ball clears the defence and arrives at a single point marked main" />
</figure>
<span class="blog-meta">
<time datetime="2026-08-06">August 6, 2026</time>
<span class="sep" aria-hidden="true">·</span>
<span>24 min read</span>
</span>
<h1 id="h-one-main-branch">An army of agents vs one main branch</h1>
<div class="blog-body">
<p class="lede">The question I get asked most now isn't about DSP. It's
how much of AetherSDR the AI wrote — and whether that makes this a
"vibe coding" project. The honest answer: a great deal of the typing,
almost none of the deciding. And the most useful prompt I have ever
written for this project did not ask for a single line of code.</p>
<p>AetherSDR started in March 2026. It is now around 2,700 commits and
380,000 lines of C++ and Qt, merged across some 1,800 pull requests,
with fifty-odd contributor identities in the log. Half a dozen distinct
AI tools touch it — Claude Code, AetherClaude, Codex, Copilot,
Gemini, Aider — but the count of tools is the boring number.
The one that matters is instances, and it has no ceiling: at minimum
one agent per contributor, running simultaneously from machines that
have never heard of each other, and a single orchestrated run on my
own bench can fan out to ninety of them at once.</p>
<p>Downstream of all of it is a community of more than two thousand
operators worldwide, running builds of AetherSDR that contain no
human-authored code, filing the bug reports our agent triages, asking
for the features our agent implements. Several of the people shaping
this project most directly have never written a line of C++. They
describe what's wrong,
or what's missing, in plain English — and
<a href="https://github.com/ten9876/aetherclaude" target="_blank" rel="noopener">AetherClaude</a>,
our autonomous triage agent, which reads every new issue, asks for
whatever's missing, and opens the pull request itself, turns it into a
change that goes through the same gate as everything else.</p>
<p>The barrier to contributing is no longer knowing how to code. It's
knowing how to describe what you need.</p>
<h2>"You'll give up in a week"</h2>
<p>Before any of this existed, I told an Elmer I was going to write a
native Linux client for FlexRadio hardware. He told me I would fail,
and that I would probably give up inside a week.</p>
<p>He wasn't being unkind. He was being experienced, which is a
different thing and usually a more reliable one. He knew the size of
the protocol surface. He knew roughly what SmartSDR had cost to build.
And he had watched people announce this exact project before and go
quiet a month later, which is a pattern worth respecting.</p>
<p>Arthur C. Clarke put it better than I can, in what's now called his
first law: when a distinguished, elderly expert says something is
possible, he is almost certainly right; when he says it is impossible,
he is very probably wrong. Amateur radio runs on Elmers, and that