-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodernization.html
More file actions
939 lines (877 loc) · 65.2 KB
/
Copy pathmodernization.html
File metadata and controls
939 lines (877 loc) · 65.2 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#14141f">
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="WWDC Journal">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="WWDC Journal">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="manifest.webmanifest">
<link rel="icon" href="app-icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="assets/icons/icon-180.png">
<title>Apple Platform Modernization Guide</title>
<style>
:root {
--bg: #0a0a0f;
--surface: #14141f;
--surface2: #1e1e2e;
--surface3: #252535;
--border: #2a2a3e;
--border-bright: #3a3a4e;
--text: #e8e8f0;
--text-dim: #8888a0;
--text-faint: #5a5a70;
--accent: #6366f1;
--accent2: #818cf8;
--green: #34d399;
--amber: #fbbf24;
--red: #f87171;
--pink: #f472b6;
--cyan: #22d3ee;
--orange: #fb923c;
--purple: #c084fc;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif; background: var(--bg); color: var(--text); overflow-x: hidden; }
/* Hero */
.hero { padding: 56px 40px 28px; text-align: center; position: relative; overflow: hidden; }
.hero::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(ellipse at center, rgba(99,102,241,0.10) 0%, transparent 60%); pointer-events: none; }
.breadcrumb { color: var(--text-dim); font-size: 0.85em; margin-bottom: 10px; }
.breadcrumb a { color: var(--accent2); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
.hero h1 { font-size: 2.8em; font-weight: 700; background: linear-gradient(135deg, #818cf8, #c084fc, #f472b6, #fb923c); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 8px; letter-spacing: -0.02em; }
.hero .sub { color: var(--text-dim); font-size: 1.05em; max-width: 720px; margin: 0 auto; line-height: 1.5; }
/* Year picker */
.year-picker-bar { max-width: 1100px; margin: 30px auto 12px; padding: 0 20px; }
.year-picker { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 20px 24px; }
.year-picker .label { color: var(--text-dim); font-size: 0.88em; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.year-picker .label strong { color: var(--text); font-weight: 600; }
.year-buttons { display: flex; flex-wrap: wrap; gap: 6px; }
.year-btn { padding: 6px 12px; border-radius: 10px; border: 1px solid var(--border); background: var(--surface2); color: var(--text-dim); cursor: pointer; font-size: 0.82em; font-weight: 600; transition: all 0.15s; font-family: inherit; }
.year-btn:hover { border-color: var(--accent); color: var(--text); }
.year-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
.year-picker .helper { margin-top: 10px; color: var(--text-faint); font-size: 0.78em; }
.year-picker .helper-active { color: var(--green); }
/* Progress bar */
.progress-bar-container { max-width: 1100px; margin: 0 auto 16px; padding: 0 20px; }
.progress-card { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 20px 24px; }
.progress-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; }
.progress-row .label { color: var(--text); font-weight: 600; font-size: 0.95em; }
.progress-row .pct { color: var(--accent2); font-weight: 700; font-size: 1.2em; }
.progress-track { height: 8px; background: var(--surface2); border-radius: 4px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, #818cf8, #c084fc, #f472b6); border-radius: 4px; transition: width 0.4s ease; }
.phase-pcts { display: flex; gap: 16px; margin-top: 14px; flex-wrap: wrap; }
.phase-pct { flex: 1; min-width: 120px; }
.phase-pct-label { color: var(--text-dim); font-size: 0.72em; margin-bottom: 4px; }
.phase-pct-mini { height: 4px; background: var(--surface2); border-radius: 2px; overflow: hidden; margin-top: 4px; }
.phase-pct-fill { height: 100%; border-radius: 2px; transition: width 0.4s ease; }
.reset-link { float: right; color: var(--text-faint); cursor: pointer; font-size: 0.78em; }
.reset-link:hover { color: var(--red); }
/* Tabs */
.tabs { display: flex; justify-content: center; gap: 8px; margin: 24px 0 32px; flex-wrap: wrap; padding: 0 20px; }
.tab { padding: 10px 22px; border-radius: 24px; border: 1px solid var(--border); background: var(--surface); color: var(--text-dim); cursor: pointer; font-size: 0.9em; transition: all 0.2s; font-family: inherit; }
.tab:hover { border-color: var(--accent); color: var(--text); }
.tab.active { background: var(--accent); border-color: var(--accent); color: white; }
/* Sections */
.section { display: none; max-width: 1200px; margin: 0 auto; padding: 0 20px 80px; }
.section.active { display: block; }
.section h2 { font-size: 1.6em; margin-bottom: 8px; color: var(--text); }
.section .section-sub { color: var(--text-dim); font-size: 0.95em; margin-bottom: 24px; line-height: 1.5; }
/* Phase cards (pathway view) */
.phase-list { display: flex; flex-direction: column; gap: 16px; }
.phase-block { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; }
.phase-header { padding: 20px 24px; cursor: pointer; display: flex; align-items: center; gap: 16px; user-select: none; transition: background 0.15s; }
.phase-header:hover { background: var(--surface2); }
.phase-num { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.1em; color: white; flex-shrink: 0; }
.phase-1 .phase-num { background: linear-gradient(135deg, #f87171, #fb923c); }
.phase-2 .phase-num { background: linear-gradient(135deg, #fb923c, #fbbf24); }
.phase-3 .phase-num { background: linear-gradient(135deg, #818cf8, #c084fc); }
.phase-4 .phase-num { background: linear-gradient(135deg, #c084fc, #f472b6); }
.phase-5 .phase-num { background: linear-gradient(135deg, #34d399, #22d3ee); }
.phase-title { flex: 1; }
.phase-title h3 { font-size: 1.15em; margin-bottom: 2px; }
.phase-title .desc { color: var(--text-dim); font-size: 0.85em; }
.phase-meta { color: var(--text-faint); font-size: 0.8em; text-align: right; flex-shrink: 0; }
.phase-meta .phase-progress-text { color: var(--accent2); font-weight: 600; }
.chevron { color: var(--text-faint); font-size: 0.8em; transition: transform 0.2s; }
.phase-block.expanded .chevron { transform: rotate(90deg); }
.phase-items { display: none; padding: 0 24px 20px; }
.phase-block.expanded .phase-items { display: block; }
/* Item cards (checkable) */
.item-list { display: flex; flex-direction: column; gap: 8px; }
.item { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; cursor: pointer; transition: all 0.15s; display: flex; gap: 14px; align-items: flex-start; }
.item:hover { border-color: var(--border-bright); }
.item.checked { opacity: 0.55; }
.item.checked .item-title { text-decoration: line-through; color: var(--text-dim); }
.item.hidden-by-filter { display: none; }
.checkbox { width: 22px; height: 22px; border-radius: 6px; border: 2px solid var(--border-bright); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; color: white; transition: all 0.15s; margin-top: 1px; }
.item.checked .checkbox { background: var(--green); border-color: var(--green); }
.item-body { flex: 1; min-width: 0; }
.item-header { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.item-title { font-weight: 600; font-size: 0.98em; color: var(--text); }
.item-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.item-tag { font-size: 0.68em; padding: 2px 8px; border-radius: 6px; background: var(--surface3); color: var(--text-dim); font-weight: 600; letter-spacing: 0.3px; text-transform: uppercase; }
.item-tag.urgency-critical { background: rgba(248,113,113,0.18); color: var(--red); }
.item-tag.urgency-high { background: rgba(251,146,60,0.18); color: var(--orange); }
.item-tag.urgency-medium { background: rgba(251,191,36,0.16); color: var(--amber); }
.item-tag.urgency-low { background: rgba(52,211,153,0.16); color: var(--green); }
.item-tag.cat-language { background: rgba(129,140,248,0.16); color: var(--accent2); }
.item-tag.cat-ui { background: rgba(244,114,182,0.16); color: var(--pink); }
.item-tag.cat-concurrency { background: rgba(192,132,252,0.16); color: var(--purple); }
.item-tag.cat-persistence { background: rgba(34,211,238,0.16); color: var(--cyan); }
.item-tag.cat-networking { background: rgba(34,211,238,0.16); color: var(--cyan); }
.item-tag.cat-privacy { background: rgba(251,191,36,0.16); color: var(--amber); }
.item-tag.cat-performance { background: rgba(52,211,153,0.16); color: var(--green); }
.item-tag.cat-ml { background: rgba(192,132,252,0.16); color: var(--purple); }
.item-tag.cat-build { background: rgba(251,146,60,0.16); color: var(--orange); }
.item-tag.cat-testing { background: rgba(129,140,248,0.16); color: var(--accent2); }
.item-tag.cat-platform { background: rgba(244,114,182,0.16); color: var(--pink); }
.item-tag.cat-critical { background: rgba(248,113,113,0.18); color: var(--red); }
.item-desc { color: var(--text-dim); font-size: 0.86em; line-height: 1.5; margin-top: 2px; }
.item-pattern { color: var(--text-faint); font-size: 0.82em; margin-top: 8px; font-family: ui-monospace, 'SF Mono', monospace; }
.item-pattern .arrow { color: var(--accent2); margin: 0 6px; }
.item-source { margin-top: 8px; font-size: 0.78em; }
.item-source a { color: var(--accent2); text-decoration: none; }
.item-source a:hover { text-decoration: underline; }
.item-year { color: var(--text-faint); font-size: 0.78em; }
/* Filter bar */
.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; padding: 16px 20px; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; align-items: center; }
.filter-group { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.filter-group-label { color: var(--text-dim); font-size: 0.78em; font-weight: 600; margin-right: 4px; text-transform: uppercase; letter-spacing: 0.3px; }
.filter-chip { padding: 5px 11px; border-radius: 14px; border: 1px solid var(--border); background: var(--surface2); color: var(--text-dim); cursor: pointer; font-size: 0.78em; transition: all 0.15s; font-family: inherit; }
.filter-chip:hover { border-color: var(--accent); color: var(--text); }
.filter-chip.active { background: var(--accent); border-color: var(--accent); color: white; }
/* Timeline */
.timeline { display: flex; flex-direction: column; gap: 8px; }
.timeline-row { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 18px 22px; display: grid; grid-template-columns: 80px 1fr; gap: 20px; align-items: start; }
.timeline-year { font-size: 1.6em; font-weight: 800; line-height: 1; }
.timeline-content { color: var(--text-dim); font-size: 0.88em; line-height: 1.6; }
.timeline-content strong { color: var(--text); font-weight: 600; }
.timeline-content .badge { display: inline-block; padding: 1px 7px; border-radius: 6px; font-size: 0.78em; font-weight: 600; margin: 0 2px; }
.timeline-content .badge.debut { background: rgba(52,211,153,0.16); color: var(--green); }
.timeline-content .badge.deprecate { background: rgba(248,113,113,0.16); color: var(--red); }
/* Gems */
.gems-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; }
.gem-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 18px; cursor: pointer; transition: all 0.15s; display: block; text-decoration: none; color: inherit; }
.gem-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.gem-text { font-size: 0.92em; line-height: 1.5; margin-bottom: 8px; }
.gem-source { color: var(--accent2); font-size: 0.78em; }
/* Empty state */
.empty-state { text-align: center; padding: 40px 20px; color: var(--text-dim); }
.empty-state .icon { font-size: 2.5em; margin-bottom: 12px; opacity: 0.4; }
@media (max-width: 700px) {
.hero h1 { font-size: 2em; }
.item-tags { flex-shrink: 0; }
.timeline-row { grid-template-columns: 60px 1fr; }
}
.footer { text-align: center; color: var(--text-dim); font-size: 0.85em; padding: 56px 20px 40px; max-width: 760px; margin: 0 auto; line-height: 1.6; }
.footer a { color: var(--accent2); text-decoration: none; }
.footer a:hover { text-decoration: underline; }
.footer-socials { display: flex; justify-content: center; gap: 14px; flex-wrap: wrap; margin-top: 22px; font-size: 0.95em; }
.footer-socials a { padding: 6px 10px; border: 1px solid var(--border); border-radius: 999px; background: rgba(20,20,31,0.62); }
.footer-socials a:hover { border-color: var(--accent2); text-decoration: none; }
</style>
</head>
<body>
<div class="hero">
<div class="breadcrumb"><a href="index.html">← WWDC Journal</a></div>
<h1>Apple Platform Modernization Guide</h1>
<p class="sub">A pathway for bringing a dated project up to current best practices — synthesized from 12 years of WWDC sessions (2014–2025). Pick your starting point, work the phases, check items off as you ship them.</p>
</div>
<div class="year-picker-bar">
<div class="year-picker">
<div class="label"><strong>When was your project last meaningfully modernized?</strong> Items predating your year are filtered out.</div>
<div class="year-buttons" id="year-buttons"></div>
<div class="helper" id="year-helper">Showing all modernization items.</div>
</div>
</div>
<div class="progress-bar-container">
<div class="progress-card">
<div class="progress-row">
<div class="label">Overall progress <span class="reset-link" onclick="resetProgress()">reset progress</span></div>
<div class="pct" id="overall-pct">0%</div>
</div>
<div class="progress-track"><div class="progress-fill" id="overall-fill" style="width:0%"></div></div>
<div class="phase-pcts" id="phase-pcts"></div>
</div>
</div>
<div class="tabs">
<div class="tab active" onclick="showSection(event,'pathway')">The Pathway</div>
<div class="tab" onclick="showSection(event,'all')">All Items</div>
<div class="tab" onclick="showSection(event,'timeline')">Year-by-Year</div>
<div class="tab" onclick="showSection(event,'gems')">Hidden Gems</div>
</div>
<div class="section active" id="sec-pathway">
<h2>The Five-Phase Pathway</h2>
<p class="section-sub">A risk-minimizing migration that delivers early wins. Click a phase to expand and check off items as you complete them. Your progress persists in this browser.</p>
<div class="phase-list" id="phase-list"></div>
</div>
<div class="section" id="sec-all">
<h2>All Modernization Items</h2>
<p class="section-sub">Every item across all phases, filterable by category and urgency. Click any source to jump to the WWDC video.</p>
<div class="filter-bar" id="filter-bar"></div>
<div class="item-list" id="all-items"></div>
</div>
<div class="section" id="sec-timeline">
<h2>Year-by-Year Reference</h2>
<p class="section-sub">What debuted and what was deprecated each year. Use this to figure out what your project predates.</p>
<div class="timeline" id="timeline"></div>
</div>
<div class="section" id="sec-gems">
<h2>Cross-Year Hidden Gems</h2>
<p class="section-sub">Insights that don't fit a tidy category but are too valuable to lose. Click any card to open the source video.</p>
<div class="gems-grid" id="gems-grid"></div>
</div>
<script>
// ============================================================================
// DATA
// ============================================================================
// Each item: id, title, phase (1-5), category, urgency, yearAvailable
// (when the modernization became available — items only appear if your project
// predates this year), oldPattern, newPattern, description, sessionId,
// sessionYear (for clickable source link).
const items = [
// ───────── PHASE 1: Stop the Bleeding ─────────
{ id:"uiscene", phase:1, cat:"critical", urgency:"critical", yearAvailable:2019,
title:"Adopt UIScene lifecycle",
description:"App will not launch in the release following iOS 26 without this. Move UIApplicationDelegate lifecycle methods to UISceneDelegate. Add UIApplicationSceneManifest to Info.plist.",
oldPattern:"UIApplicationDelegate-only lifecycle",
newPattern:"UIScene + UIWindowSceneDelegate",
sessionId:"243", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/uikit/uiscenedelegate" },
{ id:"privacy-manifest", phase:1, cat:"critical", urgency:"critical", yearAvailable:2023,
title:"Add PrivacyInfo.xcprivacy manifest",
description:"Required for App Store submission. Declare tracking, tracking domains, collected data types, and reasons for using designated APIs (file timestamps, system boot time, disk space, active keyboards, user defaults).",
oldPattern:"No privacy manifest",
newPattern:"PrivacyInfo.xcprivacy in app + every SDK",
sessionId:"10060", sessionYear:2023, docUrl:"https://developer.apple.com/documentation/bundleresources/privacy_manifest_files" },
{ id:"att-prompt", phase:1, cat:"critical", urgency:"critical", yearAvailable:2020,
title:"Add App Tracking Transparency prompt",
description:"Required if you read IDFA or send to tracking domains. Call ATTrackingManager.requestTrackingAuthorization. Add NSUserTrackingUsageDescription to Info.plist.",
oldPattern:"Direct IDFA access",
newPattern:"ATT prompt before tracking",
sessionId:"10676", sessionYear:2020, docUrl:"https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager" },
{ id:"modal-sheets", phase:1, cat:"critical", urgency:"high", yearAvailable:2019,
title:"Audit modal sheet presentation styles",
description:"iOS 13+ defaults modals to card style that users can swipe-dismiss. Set modalPresentationStyle = .fullScreen explicitly for auth flows, OR set isModalInPresentation = true to prevent accidental dismissal.",
oldPattern:"Default full-screen modals",
newPattern:".fullScreen presentation OR isModalInPresentation = true",
sessionId:"224", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/uikit/uiviewcontroller/ismodalinpresentation" },
{ id:"notarization", phase:1, cat:"critical", urgency:"critical", yearAvailable:2019,
title:"Enable Hardened Runtime + notarization (macOS)",
description:"macOS Catalina+ refuses to launch unnotarized Developer ID apps. Enable Hardened Runtime, declare entitlements, notarize via notarytool, staple the ticket.",
oldPattern:"Unnotarized Developer ID build",
newPattern:"Hardened Runtime + notarytool",
sessionId:"703", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution" },
{ id:"32bit", phase:1, cat:"critical", urgency:"high", yearAvailable:2017,
title:"Drop all 32-bit code paths",
description:"Won't run on macOS Catalina+ or iOS 11+. Build for 64-bit only. Audit dependencies. Add arm64 for Apple Silicon.",
oldPattern:"i386 / armv7 architectures",
newPattern:"x86_64 + arm64 (Universal)",
sessionId:"403", sessionYear:2017 },
{ id:"ats", phase:1, cat:"critical", urgency:"critical", yearAvailable:2016,
title:"App Transport Security: HTTPS only",
description:"All HTTP requests fail unless you've added an exception. Migrate endpoints to TLS 1.2+. Remove NSAllowsArbitraryLoads. Use NSExceptionDomains only for legacy third-party APIs you don't control.",
oldPattern:"NSAllowsArbitraryLoads = true",
newPattern:"HTTPS + TLS 1.2+ everywhere",
sessionId:"706", sessionYear:2016, docUrl:"https://developer.apple.com/documentation/security/preventing-insecure-network-connections" },
{ id:"webview", phase:1, cat:"critical", urgency:"critical", yearAvailable:2014,
title:"Replace UIWebView (audit dependencies too)",
description:"App Store submission ban since December 2020. Replace with WKWebView (or WebView from WebKit for SwiftUI in 2025+). Many SDKs pulled it in until 2020.",
oldPattern:"UIWebView",
newPattern:"WKWebView / WebView (SwiftUI)",
sessionId:"206", sessionYear:2014, docUrl:"https://developer.apple.com/documentation/webkit/wkwebview" },
{ id:"apple-silicon", phase:1, cat:"critical", urgency:"high", yearAvailable:2020,
title:"Build Universal binaries for Apple Silicon",
description:"Apps without arm64 fall back to Rosetta 2 with worse perf. Audit dependencies. Note: mach_absolute_time does NOT return nanoseconds on Apple Silicon — use clock_gettime_nsec_np or ContinuousClock.",
oldPattern:"x86_64 only / Rosetta",
newPattern:"Universal (arm64 + x86_64)",
sessionId:"10214", sessionYear:2020, docUrl:"https://developer.apple.com/documentation/apple-silicon/porting-your-macos-apps-to-apple-silicon" },
{ id:"bitcode", phase:1, cat:"critical", urgency:"medium", yearAvailable:2022,
title:"Remove Bitcode from build settings",
description:"Xcode 14+ refuses Bitcode-enabled uploads. Apple no longer requires it for any platform.",
oldPattern:"ENABLE_BITCODE = YES",
newPattern:"ENABLE_BITCODE = NO",
sessionId:"110363", sessionYear:2022, docUrl:"https://developer.apple.com/documentation/xcode/build-settings-reference" },
{ id:"odr", phase:1, cat:"critical", urgency:"medium", yearAvailable:2025,
title:"Migrate On-Demand Resources to Background Assets",
description:"ODR continues to work but Apple stopped investing. Background Assets is Apple-hosted, 200GB per app, integrates with App Store Connect.",
oldPattern:"On-Demand Resources tags",
newPattern:"Background Assets framework",
sessionId:"325", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/backgroundassets" },
{ id:"scenekit", phase:1, cat:"critical", urgency:"low", yearAvailable:2025,
title:"Plan SceneKit → RealityKit migration",
description:"SceneKit is officially soft-deprecated. RealityKit (with ECS architecture) is the path forward. Use xcrun scntool to convert assets.",
oldPattern:"SceneKit (SCNNode hierarchy)",
newPattern:"RealityKit (Entity + Components)",
sessionId:"288", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/realitykit" },
// ───────── PHASE 2: Foundation ─────────
{ id:"swift-version", phase:2, cat:"language", urgency:"high", yearAvailable:2014,
title:"Migrate Swift to current major version",
description:"Each major version unlocks ergonomics. Swift 3 was the most painful (API guidelines refactor). Use Xcode's Convert to Current Swift Syntax assistant. Adopt incrementally — one breaking change at a time.",
oldPattern:"Swift 1/2/3/4/5",
newPattern:"Swift 6.2 (or current)",
sessionId:"245", sessionYear:2025, docUrl:"https://www.swift.org/migration/" },
{ id:"codable", phase:2, cat:"language", urgency:"high", yearAvailable:2017,
title:"Replace NSCoding / JSONSerialization with Codable",
description:"Auto-synthesized JSON, plist, and binary serialization with strong types. Drops 50+ lines of boilerplate per model.",
oldPattern:"NSCoding + JSONSerialization",
newPattern:"Codable + JSONEncoder/Decoder",
sessionId:"212", sessionYear:2017, docUrl:"https://developer.apple.com/documentation/swift/codable" },
{ id:"async-urlsession", phase:2, cat:"networking", urgency:"high", yearAvailable:2021,
title:"Use async URLSession APIs",
description:"Replace completion-handler dataTask with try await URLSession.shared.data(for:). Eliminates callback nesting, plays nicely with cancellation.",
oldPattern:"URLSession.dataTask completion handler",
newPattern:"try await URLSession.shared.data(for:)",
sessionId:"10095", sessionYear:2021, docUrl:"https://developer.apple.com/documentation/foundation/urlsession/3767353-data" },
{ id:"logger", phase:2, cat:"performance", urgency:"medium", yearAvailable:2020,
title:"Replace print/NSLog with Logger",
description:"Privacy-aware (sensitive data redacted in production), structured, viewable in Console.app, lower overhead than NSLog. Use os.Logger.",
oldPattern:"print() / NSLog",
newPattern:"Logger(subsystem:category:)",
sessionId:"10168", sessionYear:2020, docUrl:"https://developer.apple.com/documentation/os/logger" },
{ id:"phpicker", phase:2, cat:"privacy", urgency:"medium", yearAvailable:2020,
title:"Use PHPickerViewController for media access",
description:"No Photos permission needed at all — system-mediated picker. Privacy-preserving by design.",
oldPattern:"UIImagePickerController + Photos auth",
newPattern:"PHPickerViewController (no auth)",
sessionId:"10652", sessionYear:2020, docUrl:"https://developer.apple.com/documentation/photokit/phpickerviewcontroller" },
{ id:"navigation-stack", phase:2, cat:"ui", urgency:"medium", yearAvailable:2022,
title:"Replace NavigationView with NavigationStack/SplitView",
description:"NavigationView is deprecated. NavigationStack supports programmatic navigation properly with NavigationPath; NavigationSplitView handles 2-3 column layouts.",
oldPattern:"NavigationView",
newPattern:"NavigationStack / NavigationSplitView",
sessionId:"10054", sessionYear:2022, docUrl:"https://developer.apple.com/documentation/swiftui/navigationstack" },
{ id:"contacts", phase:2, cat:"persistence", urgency:"medium", yearAvailable:2015,
title:"Replace AddressBook with Contacts framework",
description:"AddressBook deprecated since iOS 9. Contacts is async-friendly, Swift-first, and integrates with permissions properly.",
oldPattern:"AddressBook framework",
newPattern:"Contacts framework",
sessionId:"223", sessionYear:2015, docUrl:"https://developer.apple.com/documentation/contacts" },
{ id:"photokit", phase:2, cat:"persistence", urgency:"medium", yearAvailable:2014,
title:"Replace ALAssetsLibrary with PhotoKit",
description:"ALAssetsLibrary removed in iOS 9. PhotoKit supports Live Photos, edits, change observers, and per-asset permissions.",
oldPattern:"ALAssetsLibrary",
newPattern:"PhotoKit (PHAsset/PHFetchResult)",
sessionId:"511", sessionYear:2014, docUrl:"https://developer.apple.com/documentation/photokit" },
{ id:"unnotifications", phase:2, cat:"ui", urgency:"medium", yearAvailable:2016,
title:"Replace UILocalNotification with UNUserNotificationCenter",
description:"UILocalNotification deprecated iOS 10. UNUserNotificationCenter supports rich content, attachments, custom UI extensions.",
oldPattern:"UILocalNotification",
newPattern:"UNUserNotificationCenter",
sessionId:"707", sessionYear:2016, docUrl:"https://developer.apple.com/documentation/usernotifications/unusernotificationcenter" },
{ id:"naturallanguage", phase:2, cat:"ml", urgency:"low", yearAvailable:2018,
title:"Replace NSLinguisticTagger with NaturalLanguage",
description:"NSLinguisticTagger deprecated. NaturalLanguage framework is faster, supports custom models, integrates with Core ML.",
oldPattern:"NSLinguisticTagger",
newPattern:"NLTokenizer / NLTagger",
sessionId:"713", sessionYear:2018, docUrl:"https://developer.apple.com/documentation/naturallanguage" },
{ id:"network-fw", phase:2, cat:"networking", urgency:"low", yearAvailable:2018,
title:"Replace Secure Transport with Network framework",
description:"Secure Transport deprecated. Network framework's NWConnection has user-space TCP/UDP stack — ~30% less CPU on packet-heavy workloads, plus modern TLS by default.",
oldPattern:"Secure Transport / CFNetwork",
newPattern:"Network framework (NWConnection)",
sessionId:"715", sessionYear:2018, docUrl:"https://developer.apple.com/documentation/network" },
{ id:"sendable", phase:2, cat:"concurrency", urgency:"high", yearAvailable:2021,
title:"Add Sendable annotations to value types",
description:"Foundation for Swift 6 strict concurrency. Most value types are trivially Sendable. Mark reference types @unchecked Sendable only when manually proven thread-safe.",
oldPattern:"Implicit thread-safety",
newPattern:"explicit Sendable conformance",
sessionId:"10254", sessionYear:2021, docUrl:"https://developer.apple.com/documentation/swift/sendable" },
{ id:"layout-anchors", phase:2, cat:"ui", urgency:"low", yearAvailable:2015,
title:"Replace addConstraint(NSLayoutConstraint(...)) with NSLayoutAnchor",
description:"Type-safe, less boilerplate, easier to read. NSLayoutAnchor APIs date to iOS 9.",
oldPattern:"NSLayoutConstraint(item:attribute:relatedBy:...)",
newPattern:"view.topAnchor.constraint(equalTo: ...)",
sessionId:"218", sessionYear:2015, docUrl:"https://developer.apple.com/documentation/uikit/nslayoutanchor" },
{ id:"unarchive", phase:2, cat:"critical", urgency:"medium", yearAvailable:2018,
title:"Replace unarchiveObject(with:) with secure unarchiver",
description:"Old API is unsafe — accepts any class. Use NSKeyedUnarchiver.unarchivedObject(ofClass:from:) which restricts allowed classes.",
oldPattern:"NSKeyedUnarchiver.unarchiveObject(with:)",
newPattern:"NSKeyedUnarchiver.unarchivedObject(ofClass:from:)",
sessionId:"722", sessionYear:2018, docUrl:"https://developer.apple.com/documentation/foundation/nskeyedunarchiver" },
// ───────── PHASE 3: Adopt High-Impact Paradigms ─────────
{ id:"swift-concurrency", phase:3, cat:"concurrency", urgency:"high", yearAvailable:2021,
title:"Adopt Swift Concurrency (async/await, actors)",
description:"Convert callback-based APIs to async/throws. Replace DispatchQueue.main.async with @MainActor. Use TaskGroup where you previously used dispatch_group. Eliminates entire classes of bugs.",
oldPattern:"GCD + completion handlers",
newPattern:"async/await + actors + TaskGroup",
sessionId:"10254", sessionYear:2021, docUrl:"https://developer.apple.com/documentation/swift/concurrency" },
{ id:"swiftui", phase:3, cat:"ui", urgency:"high", yearAvailable:2019,
title:"Adopt SwiftUI (incrementally via UIHostingController)",
description:"Start with leaf screens (settings, about, onboarding). SwiftUI Lists got 6× faster, ForEach 16× faster in 2025 — they often beat hand-tuned UITableView. Use UIHostingConfiguration to put SwiftUI inside cells.",
oldPattern:"Pure UIKit",
newPattern:"UIKit + SwiftUI (UIHostingController)",
sessionId:"204", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/swiftui" },
{ id:"swiftdata", phase:3, cat:"persistence", urgency:"medium", yearAvailable:2023,
title:"Use SwiftData for new persistence",
description:"For new models. Keep Core Data for existing schema until cost-justified. Class inheritance supported in 2025. Both can share the same underlying store for incremental migration.",
oldPattern:"Core Data (new models)",
newPattern:"SwiftData with @Model",
sessionId:"10187", sessionYear:2023, docUrl:"https://developer.apple.com/documentation/swiftdata" },
{ id:"observable", phase:3, cat:"ui", urgency:"medium", yearAvailable:2023,
title:"Replace ObservableObject with @Observable macro",
description:"Less boilerplate, better SwiftUI performance (only views that read changed properties re-render). Drop @Published from every property.",
oldPattern:"ObservableObject + @Published",
newPattern:"@Observable class",
sessionId:"10149", sessionYear:2023, docUrl:"https://developer.apple.com/documentation/observation/observable()" },
{ id:"app-intents", phase:3, cat:"ml", urgency:"high", yearAvailable:2022,
title:"Expose actions via App Intents",
description:"Single biggest distribution opportunity — Spotlight, Siri, Shortcuts, Visual Intelligence, Apple Intelligence integration without per-surface code. Replaces NSUserActivity donations + custom intents.",
oldPattern:"SiriKit intent domains / NSUserActivity",
newPattern:"App Intents framework",
sessionId:"10032", sessionYear:2022, docUrl:"https://developer.apple.com/documentation/appintents" },
{ id:"vision-ocr", phase:3, cat:"ml", urgency:"low", yearAvailable:2018,
title:"Add OCR / barcode scanning via Vision",
description:"Free, on-device, 30+ languages. VNRecognizeTextRequest for OCR, VNDetectBarcodesRequest for any barcode. Don't pre-scale images — Vision rescales internally.",
oldPattern:"Custom OCR / third-party SDK",
newPattern:"Vision framework",
sessionId:"717", sessionYear:2018, docUrl:"https://developer.apple.com/documentation/vision/vnrecognizetextrequest" },
{ id:"speech", phase:3, cat:"ml", urgency:"low", yearAvailable:2016,
title:"Add speech-to-text via Speech / SpeechAnalyzer",
description:"Free, on-device since iOS 13. SpeechAnalyzer (2025) supports real-time, multiple speakers.",
oldPattern:"Custom STT / third-party SDK",
newPattern:"Speech framework / SpeechAnalyzer",
sessionId:"509", sessionYear:2016, docUrl:"https://developer.apple.com/documentation/speech" },
{ id:"foundation-models", phase:3, cat:"ml", urgency:"medium", yearAvailable:2025,
title:"Use Foundation Models for on-device LLM",
description:"~3B parameter on-device LLM. Zero cost, no API keys, offline. @Generable for typed structured output. Property declaration order matters — put summaries LAST.",
oldPattern:"External LLM API calls",
newPattern:"FoundationModels framework",
sessionId:"286", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/foundationmodels" },
{ id:"background-assets", phase:3, cat:"build", urgency:"low", yearAvailable:2025,
title:"Use Background Assets for downloadable content",
description:"Apple-hosted (200GB), integrates with App Store Connect. Replaces On-Demand Resources.",
oldPattern:"On-Demand Resources",
newPattern:"Background Assets framework",
sessionId:"325", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/backgroundassets" },
{ id:"cryptokit", phase:3, cat:"privacy", urgency:"low", yearAvailable:2019,
title:"Replace CommonCrypto with CryptoKit",
description:"Modern Swift APIs. ChaChaPoly, AES.GCM, SHA-256, public-key crypto, HPKE. Quantum-secure variants in 2025.",
oldPattern:"CommonCrypto C APIs",
newPattern:"CryptoKit",
sessionId:"709", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/cryptokit" },
{ id:"bgtaskscheduler", phase:3, cat:"performance", urgency:"low", yearAvailable:2019,
title:"Replace background fetch with BGTaskScheduler",
description:"Background fetch deprecated in iOS 13. BGTaskScheduler with BGAppRefreshTask + BGProcessingTask gives explicit scheduling control.",
oldPattern:"performFetchWithCompletionHandler",
newPattern:"BGTaskScheduler / BGAppRefreshTask",
sessionId:"707", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler" },
{ id:"diffable-datasource", phase:3, cat:"ui", urgency:"low", yearAvailable:2019,
title:"Use UICollectionViewDiffableDataSource",
description:"Eliminates batch update bugs and crashes. Apply NSDiffableDataSourceSnapshot — UIKit computes the diff and animates.",
oldPattern:"performBatchUpdates / reloadData",
newPattern:"DiffableDataSource + Snapshot",
sessionId:"220", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource" },
// ───────── PHASE 4: Polish ─────────
{ id:"liquid-glass", phase:4, cat:"ui", urgency:"high", yearAvailable:2025,
title:"Adopt Liquid Glass design system",
description:"Apps without Liquid Glass will look conspicuously dated within 12 months of iOS 26. Use .glassEffect() modifier on interactive controls; GlassEffectContainer for groups. Two variants: Regular (interactive) and Clear (decorative). Respect concentricity.",
oldPattern:"Custom translucency / pre-iOS-26 toolbars",
newPattern:".glassEffect() + GlassEffectContainer",
sessionId:"219", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/swiftui/view/glasseffect(_:in:)" },
{ id:"icon-composer", phase:4, cat:"ui", urgency:"medium", yearAvailable:2025,
title:"Refresh app icon with Icon Composer",
description:"New tool for the new icon system (Liquid Glass icons). Replaces the manual asset catalog approach for app icons.",
oldPattern:"AppIcon asset catalog (multiple PNGs)",
newPattern:"Icon Composer single source",
sessionId:"361", sessionYear:2025, docUrl:"https://developer.apple.com/documentation/xcode/configuring-your-app-icon" },
{ id:"sf-symbols", phase:4, cat:"ui", urgency:"medium", yearAvailable:2019,
title:"Replace custom icon assets with SF Symbols",
description:"Free, animated, weight/scale-aware, every Apple platform. SF Symbols 7 (2025) adds variable draw, gradients, Magic Replace.",
oldPattern:"Custom PNG/PDF icon assets",
newPattern:"Image(systemName:) / UIImage(systemName:)",
sessionId:"206", sessionYear:2019, docUrl:"https://developer.apple.com/sf-symbols/" },
{ id:"dynamic-type", phase:4, cat:"ui", urgency:"medium", yearAvailable:2014,
title:"Audit Dynamic Type support",
description:"Every text view should scale with user's preferred text size. UIFontMetrics in UIKit; SwiftUI auto-handles. Use @ScaledMetric for non-text scalars.",
oldPattern:"Hardcoded font sizes",
newPattern:"UIFontMetrics / .font(.body)",
sessionId:"245", sessionYear:2014, docUrl:"https://developer.apple.com/documentation/uikit/uifontmetrics" },
{ id:"dark-mode", phase:4, cat:"ui", urgency:"medium", yearAvailable:2019,
title:"Audit Dark Mode (semantic colors)",
description:"Replace UIColor.white/UIColor(red:...) with semantic colors: .systemBackground, .label, .secondaryLabel. SwiftUI has Color(uiColor: .label) etc.",
oldPattern:"Hardcoded UIColor values",
newPattern:"Semantic system colors",
sessionId:"214", sessionYear:2019, docUrl:"https://developer.apple.com/documentation/uikit/uicolor/standard_colors" },
{ id:"voiceover", phase:4, cat:"ui", urgency:"high", yearAvailable:2014,
title:"Add VoiceOver labels everywhere",
description:"Every interactive element needs an accessibilityLabel. Buttons should be Buttons (not Views with onTapGesture). Test with VoiceOver enabled.",
oldPattern:"No accessibility labels",
newPattern:"accessibilityLabel / .accessibilityLabel()",
sessionId:"206", sessionYear:2014, docUrl:"https://developer.apple.com/documentation/uikit/accessibility_for_uikit" },
{ id:"a11y-labels", phase:4, cat:"ui", urgency:"medium", yearAvailable:2025,
title:"File Accessibility Nutrition Labels",
description:"App Store Connect feature — declare which accessibility features your app supports.",
oldPattern:"No accessibility nutrition declaration",
newPattern:"Accessibility Nutrition Labels in App Store Connect",
sessionId:"224", sessionYear:2025, docUrl:"https://developer.apple.com/news/?id=jcwnehy0" },
{ id:"widgets", phase:4, cat:"ui", urgency:"low", yearAvailable:2020,
title:"Add Widgets / Live Activities",
description:"WidgetKit (2020) for Home Screen / Lock Screen widgets. Live Activities (2022) for time-sensitive updates. Interactive widgets (2023) for in-place actions.",
oldPattern:"Today extension widgets",
newPattern:"WidgetKit + Live Activities",
sessionId:"10034", sessionYear:2020, docUrl:"https://developer.apple.com/documentation/widgetkit" },
{ id:"passkeys", phase:4, cat:"privacy", urgency:"medium", yearAvailable:2022,
title:"Add Passkey support for authentication",
description:"Phishing-resistant, no passwords to leak, syncs via iCloud Keychain. Account creation API (2025) and automatic passkey upgrades.",
oldPattern:"Password-only authentication",
newPattern:"Passkeys (ASAuthorizationPlatformPublicKeyCredentialProvider)",
sessionId:"10092", sessionYear:2022, docUrl:"https://developer.apple.com/passkeys/" },
{ id:"sign-in-apple", phase:4, cat:"privacy", urgency:"high", yearAvailable:2019,
title:"Offer Sign In with Apple",
description:"Required if you offer third-party social sign-in (Google/Facebook). Privacy-preserving relay email is a great primary auth option too.",
oldPattern:"Only third-party SSO (Google/Facebook)",
newPattern:"Add Sign In with Apple",
sessionId:"706", sessionYear:2019, docUrl:"https://developer.apple.com/sign-in-with-apple/" },
{ id:"swift-charts", phase:4, cat:"ui", urgency:"low", yearAvailable:2022,
title:"Use Swift Charts (delete custom charting)",
description:"Composable, accessible, animated charts. Bar/line/area/pie/scatter/sector/heatmap. Charts 3D in 2025.",
oldPattern:"Custom CGContext drawing / third-party chart libs",
newPattern:"Swift Charts (Chart { ... })",
sessionId:"10136", sessionYear:2022, docUrl:"https://developer.apple.com/documentation/charts" },
// ───────── PHASE 5: Forward Discipline ─────────
{ id:"strict-concurrency", phase:5, cat:"concurrency", urgency:"medium", yearAvailable:2024,
title:"Enable Swift 6 strict concurrency mode",
description:"Adopt as soon as your dependencies support it. Enable in stages with -strict-concurrency=minimal/targeted/complete. Catches data races at compile time.",
oldPattern:"Implicit concurrency assumptions",
newPattern:"Strict Concurrency Checking = complete",
sessionId:"10169", sessionYear:2024, docUrl:"https://www.swift.org/migration/documentation/migrationguide/" },
{ id:"swift-testing", phase:5, cat:"testing", urgency:"medium", yearAvailable:2024,
title:"Write new tests with Swift Testing",
description:"Modern replacement for XCTest. @Test, #expect, parameterized tests, tags. Async-native (no XCTestExpectation boilerplate).",
oldPattern:"XCTestCase + XCTAssert*",
newPattern:"@Test func + #expect / #require",
sessionId:"10179", sessionYear:2024, docUrl:"https://developer.apple.com/documentation/testing" },
{ id:"hitch-metric", phase:5, cat:"performance", urgency:"low", yearAvailable:2023,
title:"Add XCTHitchMetric to CI",
description:"Catch animation/scroll hitch regressions before they ship. Pairs with XCTOSSignpostMetric for custom signposts.",
oldPattern:"Manual perf checks",
newPattern:"XCTHitchMetric in CI",
sessionId:"10037", sessionYear:2023, docUrl:"https://developer.apple.com/documentation/xctest/xcthitchmetric" },
{ id:"metrickit", phase:5, cat:"performance", urgency:"low", yearAvailable:2018,
title:"Integrate MetricKit",
description:"Receive on-device metrics from real users — battery, hangs, scroll hitches, disk writes. Critical for understanding production performance.",
oldPattern:"Synthetic perf testing only",
newPattern:"MetricKit in production",
sessionId:"408", sessionYear:2018, docUrl:"https://developer.apple.com/documentation/metrickit" },
{ id:"mergeable-libs", phase:5, cat:"build", urgency:"low", yearAvailable:2023,
title:"Enable Mergeable Libraries",
description:"1.5x–3x launch time improvement for apps with many frameworks. Single build setting, no code changes.",
oldPattern:"Many dynamic frameworks",
newPattern:"Mergeable libraries",
sessionId:"10268", sessionYear:2023, docUrl:"https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries" },
{ id:"wwdc-ritual", phase:5, cat:"build", urgency:"low", yearAvailable:2014,
title:"Schedule WWDC review each year",
description:"Half-day to review What's New in Swift, What's New in SwiftUI/UIKit, What's New in Xcode. Catch deprecations and new opportunities while they're fresh.",
oldPattern:"Ad-hoc adoption (once a year skim)",
newPattern:"Calendared annual WWDC sweep",
sessionId:"101", sessionYear:2025, docUrl:"https://developer.apple.com/wwdc/" },
{ id:"beta-day-one", phase:5, cat:"build", urgency:"low", yearAvailable:2014,
title:"Test against major-version SDK on day one of beta",
description:"Find breaking changes before users do. WWDC25 demonstrated apps breaking on iOS 26 in unexpected ways (UIScene mandatory).",
oldPattern:"Wait for public release",
newPattern:"Build against beta on day 1",
sessionId:"102", sessionYear:2025, docUrl:"https://developer.apple.com/download/" },
];
// ============================================================================
// PHASES & TIMELINE
// ============================================================================
const phases = [
{ num:1, title:"Stop the Bleeding", desc:"Make the app build, sign, ship on current Xcode + current OS. 1–2 weeks." },
{ num:2, title:"Modernize the Foundation", desc:"Painful but mechanical. Unlocks everything else. 3–4 weeks." },
{ num:3, title:"Adopt High-Impact Paradigms", desc:"The big wins — Swift Concurrency, SwiftUI, SwiftData, App Intents. 4–8 weeks." },
{ num:4, title:"Polish", desc:"Visible improvements that delight users — Liquid Glass, accessibility, AI features. 2–4 weeks." },
{ num:5, title:"Forward Discipline", desc:"Don't let it rot again. Annual rituals, beta testing, CI hygiene." },
];
const timeline = [
{ year:2014, debuts:"Swift 1, App Extensions, HealthKit, HomeKit, CloudKit, Metal, WKWebView, PhotoKit, Touch ID API, Adaptive UI / Size Classes, Continuity / Handoff, SceneKit on iOS, WatchKit preview", deprecates:"ALAssetsLibrary, UIWebView (later)", platforms:"iOS 8, OS X Yosemite" },
{ year:2015, debuts:"Swift 2, Protocol-Oriented Programming, iPad multitasking, watchOS 2 native apps, tvOS, App Thinning, Bitcode, GameplayKit, NSLayoutAnchor, Search APIs (Core Spotlight, NSUserActivity), UIStackView, Content Blockers", deprecates:"NSURLConnection, AddressBook", platforms:"iOS 9, watchOS 2, tvOS, El Capitan" },
{ year:2016, debuts:"Swift 3 (huge breaking renames), SiriKit, iMessage Apps, CallKit, watchOS 3 redesign, APFS sneak, Speech framework, Differential Privacy, Universal Clipboard, UNUserNotifications", deprecates:"UILocalNotification, dispatch C APIs (in Swift), SLSpeechRecognizer", platforms:"iOS 10, macOS Sierra" },
{ year:2017, debuts:"ARKit, Core ML, Vision framework, Drag and Drop, Files app, iOS 11 design refresh, Swift 4 + Codable, Metal 2, HEIF/HEVC, PDFKit cross-platform, MapKit clustering", deprecates:"NSCoding (in favor of Codable), default UIImagePickerController in many cases", platforms:"iOS 11, High Sierra" },
{ year:2018, debuts:"iOS 12 PERFORMANCE focus, Siri Shortcuts API + Shortcuts app, ARKit 2, USDZ, Create ML, Core ML 2, NaturalLanguage, Network framework, Marzipan preview, watchOS 5, Mojave Dark Mode, Memoji", deprecates:"NSLinguisticTagger, OpenGL ES, Secure Transport", platforms:"iOS 12, macOS Mojave" },
{ year:2019, debuts:"SwiftUI, Combine, Catalyst, iPadOS, Sign In with Apple, Dark Mode, Swift 5.1 (property wrappers, function builders), RealityKit, SF Symbols, Swift Package Manager in Xcode, BGTaskScheduler, CryptoKit, macOS notarization", deprecates:"UIWebView (banned 2020), 32-bit on macOS Catalina", platforms:"iOS 13, iPadOS, macOS Catalina, watchOS 6" },
{ year:2020, debuts:"Apple Silicon transition, Big Sur design, WidgetKit, App Clips, Swift 5.3, watchOS 7 sleep tracking, App Tracking Transparency, Logger (os.Logger), PHPickerViewController, ARKit 4 (location anchors)", deprecates:"UIWebView (December 2020 ban), 32-bit on iOS", platforms:"iOS 14, macOS Big Sur, watchOS 7" },
{ year:2021, debuts:"Swift Concurrency (async/await, actors, structured concurrency), SharePlay, Object Capture, RealityKit 2, Xcode Cloud, DocC, Focus modes, Live Text, TestFlight Mac, GroupActivities", deprecates:"None major; Combine begins to wane", platforms:"iOS 15, macOS Monterey, watchOS 8" },
{ year:2022, debuts:"SwiftUI Charts, NavigationStack/SplitView, App Intents, Lock Screen widgets, Live Activities, Layout protocol, Swift 5.7 (regex, generic constraints), watchOS 9", deprecates:"NavigationView, Bitcode (removed)", platforms:"iOS 16, macOS Ventura, watchOS 9" },
{ year:2023, debuts:"visionOS reveal, SwiftData, Observation framework, Swift macros, parameter packs, interactive widgets, mergeable libraries, watchOS 10 redesign, L4S networking, StoreKit 2 maturity, Reality Composer Pro", deprecates:"Combine ObservableObject pattern phases out", platforms:"iOS 17, macOS Sonoma, visionOS 1, watchOS 10" },
{ year:2024, debuts:"Apple Intelligence (Writing Tools, Genmoji, Image Playground), Swift 6 strict concurrency, Swift Testing, visionOS 2, Translation framework, Privacy manifest enforcement, WidgetKit interactivity (full), watchOS 11", deprecates:"XCTest in favor of Swift Testing, pre-Swift-6 unchecked concurrency", platforms:"iOS 18, macOS Sequoia, visionOS 2, watchOS 11" },
{ year:2025, debuts:"Liquid Glass, Foundation Models on-device LLM, Swift 6.2 main-actor-by-default, visionOS 26, AlarmKit, EnergyKit, PaperKit, PermissionKit, SpeechAnalyzer, Background Assets, Wi-Fi Aware, App Intents + LLM integration, quantum-secure TLS default", deprecates:"UIScene becomes mandatory next year, SceneKit soft-deprecated, On-Demand Resources soft-deprecated", platforms:"iOS 26, macOS 26, visionOS 26, watchOS 26" },
];
const gems = [
{ text:"mach_absolute_time is NOT nanoseconds on Apple Silicon — most common porting bug, 40× slowdowns.", source:"WWDC20-10214", year:2020, id:"10214" },
{ text:"Data.dropFirst() is O(n), not O(1). Use Slice or new Span types.", source:"WWDC25-312", year:2025, id:"312" },
{ text:"Optional in Swift is just an enum: enum Optional<T> { case none; case some(T) }.", source:"WWDC14-403", year:2014, id:"403" },
{ text:"APFS clones make FileManager.copyItem essentially free regardless of file size.", source:"WWDC17-715", year:2017, id:"715" },
{ text:"URL.lines and URL.bytes are AsyncSequence accessors — stream remote JSON-Lines or local files with no setup.", source:"WWDC21-10058", year:2021, id:"10058" },
{ text:"GroupSession messaging is end-to-end encrypted, even from Apple — free private cross-device channel.", source:"WWDC21-10183", year:2021, id:"10183" },
{ text:"Catalyst silently no-ops file data protection. Your sensitive iOS files are NOT encrypted at rest on Mac. Use Keychain.", source:"WWDC19-205", year:2019, id:"205" },
{ text:"Personal Voice runs entirely on-device; your app can use it via AVSpeechSynthesisVoice.personalVoice.", source:"WWDC23-10033", year:2023, id:"10033" },
{ text:"Core Data + SwiftData can share the SAME underlying store — incremental migration screen-by-screen.", source:"WWDC23-10189", year:2023, id:"10189" },
{ text:"L4S networking can drop latency from 100ms → 5ms WITHOUT throughput loss — opt in via one Network framework flag.", source:"WWDC23-10004", year:2023, id:"10004" },
{ text:"Mergeable libraries can shrink launch time 1.5x–3x — single build setting, no code changes.", source:"WWDC23-10268", year:2023, id:"10268" },
{ text:"iOS 12 made Auto Layout linear-complexity — apps with deep view hierarchies got massive wins from JUST relinking.", source:"WWDC18-220", year:2018, id:"220" },
{ text:"Network.framework's user-space TCP/UDP stack lives in your app's address space — ~30% less CPU on packet-heavy workloads.", source:"WWDC18-715", year:2018, id:"715" },
{ text:"Custom keyboards BLOCK SMS code autofill — iOS needs the system keyboard to inject the OTP.", source:"WWDC18-204", year:2018, id:"204" },
{ text:"Watch Keychain inherits wrist-detection lock — credentials become inaccessible the moment the user takes off the watch.", source:"WWDC15-105", year:2015, id:"105" },
{ text:"dispatch_semaphore_wait blocks priority inversion resolution. Use dispatch_block_wait or async/await.", source:"WWDC15-718", year:2015, id:"718" },
{ text:"NEVER pre-scale images for Vision — framework rescales internally, pre-scaling burns work twice.", source:"WWDC17-506", year:2017, id:"506" },
{ text:"Workout SiriKit intents with .handleInApp run the app in the background — pause workouts via voice with phone locked.", source:"WWDC17-217", year:2017, id:"217" },
{ text:"Property declaration order in @Generable affects Foundation Models output quality — put summaries LAST.", source:"WWDC25-301", year:2025, id:"301" },
{ text:"session.prewarm() eliminates Foundation Models asset loading latency — call when user navigates to a screen that uses AI.", source:"WWDC25-301", year:2025, id:"301" },
{ text:"includeSchemaInPrompt = false saves hundreds of tokens once you've provided examples in instructions.", source:"WWDC25-301", year:2025, id:"301" },
{ text:"Quantum-secure TLS is enabled by DEFAULT in iOS 26 — your app gets post-quantum protection automatically.", source:"WWDC25-314", year:2025, id:"314" },
{ text:"Background pushes are silently rate-limited — 14 pushes may produce only 7 launches.", source:"WWDC20-10063", year:2020, id:"10063" },
{ text:"Audio threads on Apple Silicon MUST join os_workgroup or they may be scheduled on E-cores and glitch.", source:"WWDC20-10224", year:2020, id:"10224" },
{ text:"Apple GPU SIMD group size is 32, not 64 — compute shaders that assumed otherwise produce visual artifacts.", source:"WWDC20-10602", year:2020, id:"10602" },
{ text:"Eytzinger layout for cache-friendly binary search is dramatically faster than sorted arrays.", source:"WWDC25-312", year:2025, id:"312" },
{ text:"SwiftUI's visualEffect modifier runs on background threads — never access @MainActor state inside.", source:"WWDC25-266", year:2025, id:"266" },
{ text:"Swift Concurrency's cooperative pool spawns only as many threads as CPU cores — thread explosion is impossible by construction.", source:"WWDC21-10254", year:2021, id:"10254" },
];
// ============================================================================
// STATE
// ============================================================================
const STORAGE_KEY = "wwdc_modernization_progress_v1";
const YEAR_KEY = "wwdc_modernization_year_v1";
let checkedItems = new Set();
let selectedYear = null; // null = show all
let currentFilters = { category: "all", urgency: "all" };
function loadState() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) checkedItems = new Set(JSON.parse(raw));
const yr = localStorage.getItem(YEAR_KEY);
if (yr && yr !== "all") selectedYear = parseInt(yr, 10);
} catch (e) {}
}
function saveState() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify([...checkedItems]));
localStorage.setItem(YEAR_KEY, selectedYear === null ? "all" : String(selectedYear));
} catch (e) {}
}
function resetProgress() {
if (!confirm("Reset all check progress? This cannot be undone.")) return;
checkedItems.clear();
saveState();
renderAll();
}
// ============================================================================
// HELPERS
// ============================================================================
function sessionUrl(year, id) {
const cleanId = String(id).replace(/[^0-9]/g, '');
return `https://developer.apple.com/videos/play/wwdc${year}/${cleanId}/`;
}
// Items applicable to user: those whose yearAvailable >= selectedYear
function applicableItems() {
if (selectedYear === null) return items;
return items.filter(it => it.yearAvailable >= selectedYear);
}
function toggleItem(id) {
if (checkedItems.has(id)) checkedItems.delete(id);
else checkedItems.add(id);
saveState();
renderAll();
}
// ============================================================================
// RENDER
// ============================================================================
function renderYearPicker() {
const years = [null, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024];
document.getElementById("year-buttons").innerHTML = years.map(y => {
const label = y === null ? "All" : y;
const active = (selectedYear === y) ? "active" : "";
return `<button class="year-btn ${active}" onclick="setYear(${y === null ? "null" : y})">${label}</button>`;
}).join("");
const helper = document.getElementById("year-helper");
if (selectedYear === null) {
helper.textContent = "Showing all modernization items.";
helper.className = "helper";
} else {
const n = applicableItems().length;
helper.innerHTML = `Showing <strong>${n}</strong> items relevant to a project last refreshed in ${selectedYear}. Older modernizations are filtered out.`;
helper.className = "helper helper-active";
}
}
function setYear(y) {
selectedYear = y;
saveState();
renderAll();
}
function renderProgress() {
const apps = applicableItems();
const total = apps.length;
const done = apps.filter(it => checkedItems.has(it.id)).length;
const pct = total === 0 ? 0 : Math.round(done / total * 100);
document.getElementById("overall-pct").textContent = `${pct}%`;
document.getElementById("overall-fill").style.width = `${pct}%`;
// Per-phase progress
const phaseGradients = {
1: "linear-gradient(90deg, #f87171, #fb923c)",
2: "linear-gradient(90deg, #fb923c, #fbbf24)",
3: "linear-gradient(90deg, #818cf8, #c084fc)",
4: "linear-gradient(90deg, #c084fc, #f472b6)",
5: "linear-gradient(90deg, #34d399, #22d3ee)",
};
document.getElementById("phase-pcts").innerHTML = phases.map(ph => {
const phItems = apps.filter(it => it.phase === ph.num);
const phDone = phItems.filter(it => checkedItems.has(it.id)).length;
const phPct = phItems.length === 0 ? 0 : Math.round(phDone / phItems.length * 100);
return `<div class="phase-pct">
<div class="phase-pct-label">Phase ${ph.num} · ${phDone}/${phItems.length}</div>
<div class="phase-pct-mini"><div class="phase-pct-fill" style="width:${phPct}%; background:${phaseGradients[ph.num]};"></div></div>
</div>`;
}).join("");
}
function itemHTML(it) {
const checked = checkedItems.has(it.id);
const links = [];
if (it.sessionId) links.push(`<a href="${sessionUrl(it.sessionYear, it.sessionId)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">WWDC${String(it.sessionYear).slice(-2)}-${it.sessionId} →</a>`);
if (it.docUrl) links.push(`<a href="${it.docUrl}" target="_blank" rel="noopener" onclick="event.stopPropagation()">📘 Apple Docs →</a>`);
const sourceLink = links.length ? `<div class="item-source">${links.join(' · ')}</div>` : "";
const pattern = it.oldPattern ? `<div class="item-pattern">${escapeHtml(it.oldPattern)}<span class="arrow">→</span>${escapeHtml(it.newPattern || "")}</div>` : "";
return `<div class="item ${checked?'checked':''}" data-id="${it.id}" data-cat="${it.cat}" data-urgency="${it.urgency}" onclick="toggleItem('${it.id}')">
<div class="checkbox">${checked ? '✓' : ''}</div>
<div class="item-body">
<div class="item-header">
<div class="item-title">${escapeHtml(it.title)}</div>
<div class="item-tags">
<span class="item-tag urgency-${it.urgency}">${it.urgency}</span>
<span class="item-tag cat-${it.cat}">${it.cat}</span>
<span class="item-year">since ${it.yearAvailable}</span>
</div>
</div>
<div class="item-desc">${escapeHtml(it.description)}</div>
${pattern}
${sourceLink}
</div>
</div>`;
}
function renderPathway() {
const apps = applicableItems();
document.getElementById("phase-list").innerHTML = phases.map(ph => {
const phItems = apps.filter(it => it.phase === ph.num);
const phDone = phItems.filter(it => checkedItems.has(it.id)).length;
const phPct = phItems.length === 0 ? 0 : Math.round(phDone / phItems.length * 100);
if (phItems.length === 0) {
return `<div class="phase-block phase-${ph.num}">
<div class="phase-header" onclick="togglePhase(this)">
<div class="phase-num">${ph.num}</div>
<div class="phase-title"><h3>${ph.title}</h3><div class="desc">${ph.desc}</div></div>
<div class="phase-meta"><div>No applicable items</div><div class="phase-progress-text">—</div></div>
</div>
</div>`;
}
return `<div class="phase-block phase-${ph.num} ${ph.num === 1 ? 'expanded' : ''}">
<div class="phase-header" onclick="togglePhase(this)">
<div class="phase-num">${ph.num}</div>
<div class="phase-title"><h3>${ph.title}</h3><div class="desc">${ph.desc}</div></div>
<div class="phase-meta">
<div class="phase-progress-text">${phDone}/${phItems.length} · ${phPct}%</div>
<div class="chevron">›</div>
</div>
</div>
<div class="phase-items">
<div class="item-list">${phItems.map(itemHTML).join("")}</div>
</div>
</div>`;
}).join("");
}
function togglePhase(headerEl) {
headerEl.parentElement.classList.toggle("expanded");
}
function renderAllItems() {
const apps = applicableItems();
const cats = [...new Set(apps.map(it => it.cat))];
const urgs = ["critical", "high", "medium", "low"];
document.getElementById("filter-bar").innerHTML = `
<div class="filter-group">
<span class="filter-group-label">Urgency</span>
<button class="filter-chip ${currentFilters.urgency==='all'?'active':''}" onclick="setFilter('urgency','all')">All</button>
${urgs.map(u => `<button class="filter-chip ${currentFilters.urgency===u?'active':''}" onclick="setFilter('urgency','${u}')">${u}</button>`).join("")}
</div>
<div class="filter-group">
<span class="filter-group-label">Category</span>
<button class="filter-chip ${currentFilters.category==='all'?'active':''}" onclick="setFilter('category','all')">All</button>
${cats.map(c => `<button class="filter-chip ${currentFilters.category===c?'active':''}" onclick="setFilter('category','${c}')">${c}</button>`).join("")}
</div>
`;
const filtered = apps.filter(it =>
(currentFilters.urgency === "all" || it.urgency === currentFilters.urgency) &&
(currentFilters.category === "all" || it.cat === currentFilters.category)
);
if (filtered.length === 0) {
document.getElementById("all-items").innerHTML = `<div class="empty-state"><div class="icon">∅</div>No items match these filters.</div>`;
return;
}
document.getElementById("all-items").innerHTML = filtered.map(itemHTML).join("");
}
function setFilter(kind, value) {
currentFilters[kind] = value;
renderAllItems();
}
function renderTimeline() {
document.getElementById("timeline").innerHTML = timeline.map(t => {
const debutBadges = t.debuts.split(",").map(s => `<span class="badge debut">+ ${s.trim()}</span>`).join(" ");
const deprecBadges = t.deprecates && t.deprecates !== "None major; Combine begins to wane"
? t.deprecates.split(",").map(s => `<span class="badge deprecate">− ${s.trim()}</span>`).join(" ")
: "";
return `<div class="timeline-row">
<div class="timeline-year" style="color:${yearColor(t.year)}">${t.year}</div>
<div class="timeline-content">
<div style="margin-bottom:6px"><strong>${escapeHtml(t.platforms)}</strong></div>
<div style="margin-bottom:6px"><strong>Debuts:</strong> ${debutBadges}</div>
${deprecBadges ? `<div><strong>Deprecates:</strong> ${deprecBadges}</div>` : ""}
</div>
</div>`;
}).join("");
}
function yearColor(y) {
const map = { 2014:"#f59e0b", 2015:"#84cc16", 2016:"#14b8a6", 2017:"#f97316", 2018:"#ec4899", 2019:"#06b6d4", 2020:"#ef4444", 2021:"#a78bfa", 2022:"#f87171", 2023:"#22d3ee", 2024:"#fb923c", 2025:"#c084fc" };
return map[y] || "#818cf8";
}
function renderGems() {
document.getElementById("gems-grid").innerHTML = gems.map(g => `
<a class="gem-card" href="${sessionUrl(g.year, g.id)}" target="_blank" rel="noopener">
<div class="gem-text">${escapeHtml(g.text)}</div>
<div class="gem-source">${g.source} →</div>
</a>
`).join("");
}
function renderAll() {
renderYearPicker();
renderProgress();
renderPathway();
renderAllItems();
}
function showSection(ev, name) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('sec-' + name).classList.add('active');
ev.target.classList.add('active');
}
function escapeHtml(s) {
return String(s).replace(/[<>&'"]/g, c => ({'<':'<','>':'>','&':'&',"'":''','"':'"'}[c]));
}
// ============================================================================
// INIT
// ============================================================================
loadState();
renderAll();
renderTimeline();
renderGems();
</script>
<div class="footer">
Each dashboard contains: deep transcript analysis (best practices, hidden gems, performance tips, migration guidance), curated learning pathways with prerequisites, session clusters, transcript-vs-docs deltas, and an interactive dependency graph. <br><br>
Source transcripts from <a href="https://nonstrict.eu/wwdcindex/" target="_blank" rel="noopener">Nonstrict WWDC Index</a> and <a href="https://gist.github.com/auramagi/9c040c2233dfe71c24c76942e186f788" target="_blank" rel="noopener">auramagi gist</a>. Independent project, not affiliated with Apple. Code is <a href="LICENSE">MIT</a>; original editorial content and curation are <a href="CONTENT-LICENSE.md">CC BY-NC-ND 4.0</a>. See <a href="THIRD_PARTY_NOTICES.md">third-party notices</a>.
<div class="footer-socials" aria-label="Creator social links">
<a href="https://github.com/need2edit" target="_blank" rel="noopener">GitHub</a>
<a href="https://x.com/need2edit" target="_blank" rel="noopener">X/Twitter</a>
<a href="https://www.linkedin.com/in/youngjake/" target="_blank" rel="noopener">LinkedIn</a>
</div>
</div>
</body>
</html>